From 0edf2068935aa38febf8a67f5feb8c8856c971b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Antonio=20Ben=C3=ADtez=20De=20La=20Portilla?= Date: Fri, 9 May 2025 14:41:07 -0600 Subject: [PATCH 001/116] feat: Implementar funcionalidad para leer set de cuotas y agregar nuevas rutas --- .../Controladores/leerSetCuotas.controller.js | 45 +++++++++++++ .../Repositorios/leerSetCuotasRepositorio.js | 34 ++++++++++ .../RutasIndividuales/leerSetCuotas.routes.js | 67 +++++++++++++++++++ Cuotas/Rutas/indexCuotas.routes.js | 16 ++--- Utilidades/Constantes/consultasCuotas.js | 13 +++- Utilidades/Constantes/rutas.js | 1 + 6 files changed, 166 insertions(+), 10 deletions(-) create mode 100644 Cuotas/Controladores/leerSetCuotas.controller.js create mode 100644 Cuotas/Datos/Repositorios/leerSetCuotasRepositorio.js create mode 100644 Cuotas/Rutas/RutasIndividuales/leerSetCuotas.routes.js diff --git a/Cuotas/Controladores/leerSetCuotas.controller.js b/Cuotas/Controladores/leerSetCuotas.controller.js new file mode 100644 index 00000000..b9fc419f --- /dev/null +++ b/Cuotas/Controladores/leerSetCuotas.controller.js @@ -0,0 +1,45 @@ +const repositorio = require('@altertex/cuota/repos/leerSetCuotasRepositorio'); +const MENSAJES_CUOTAS = require('@altertex/util/const/mensajesCuotas'); + +/** + * Lee un conjunto de cuotas desde la base de datos utilizando su ID. + * + * Valida el parámetro `idCuota` y obtiene la información de las cuotas a través del repositorio. + * Si las cuotas no son encontradas o el parámetro es inválido, retorna un error. + * + * @param {Express.Request} req - La solicitud HTTP que contiene el `idCuota` en el cuerpo. + * @param {Express.Response} res - La respuesta HTTP para enviar el resultado al cliente. + * @returns {Promise} Responde con las cuotas encontradas o un mensaje de error. + * + * @see [RF33 Leer set cuotas](https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF38) + */ + +exports.leerSetCuotas = async (req, res) => { + const idSetCuota = parseInt(req.body.idSetCuota); + + if (isNaN(idSetCuota)) { + return res + .status(MENSAJES_CUOTAS.PARAMETROS_INVALIDOS.codigo) + .json({ mensaje: MENSAJES_CUOTAS.PARAMETROS_INVALIDOS.mensaje }); + } + + try { + const setCuota = await repositorio.obtenerCuotasPorId(idSetCuota); + + if (!setCuota) { + return res + .status(MENSAJES_CUOTAS.CUOTAS_NO_ENCONTRADAS.codigo) + .json({ mensaje: MENSAJES_CUOTAS.CUOTAS_NO_ENCONTRADAS.mensaje }); + } + + return res.status(MENSAJES_CUOTAS.CUOTAS_OBTENIDAS.codigo).json({ + mensaje: MENSAJES_CUOTAS.CUOTAS_OBTENIDAS.mensaje, + setCuota, + }); + } catch (error) { + console.error('Error al consultar Set cuotas:', error); + return res + .status(MENSAJES_CUOTAS.ERROR_OBTENER_CUOTAS.codigo) + .json({ mensaje: MENSAJES_CUOTAS.ERROR_OBTENER_CUOTAS.mensaje }); + } +}; diff --git a/Cuotas/Datos/Repositorios/leerSetCuotasRepositorio.js b/Cuotas/Datos/Repositorios/leerSetCuotasRepositorio.js new file mode 100644 index 00000000..e8d4e3c4 --- /dev/null +++ b/Cuotas/Datos/Repositorios/leerSetCuotasRepositorio.js @@ -0,0 +1,34 @@ +const correrQuery = require('@altertex/util/ser/correrQuery'); +const CONSULTAS_CUOTAS = require('@altertex/util/const/consultasCuotas'); + +/** + * Obtiene un conjunto de cuotas desde la base de datos mediante su ID. + * + * Ejecuta una consulta SQL y retorna el primer conjunto de cuotas encontrado o `null` si no existe. + * + * @param {number|string} idSetCuota de cuota a buscar. + * @returns {Promise} El conjunto de cuotas encontrado o `null` si no existe. + * @throws {Error} Si ocurre un error al ejecutar la consulta. + * + * @see [RF33 Leer conjunto de cuotas](https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF38) + */ + +exports.obtenerSetCuotaPorId = async (idSetCuota) => { + const query = CONSULTAS_CUOTAS.LEER_CUOTA_SET; + + const resultado = await correrQuery(query, [idSetCuota]); + + if (resultado.length === 0) return null; + + const setCuota = { + idSetCuota: resultado[0].idSetCuota, + nombre: resultado[0].nombre, + descripcion: resultado[0].descripcion, + puntos: resultado[0].puntos, + multiplicador: resultado[0].multiplicador, + periodoRenovacion: resultado[0].periodoRenovacion, + renovacion: resultado[0].renovacion, + }; + + return setCuota; +}; diff --git a/Cuotas/Rutas/RutasIndividuales/leerSetCuotas.routes.js b/Cuotas/Rutas/RutasIndividuales/leerSetCuotas.routes.js new file mode 100644 index 00000000..db6f4786 --- /dev/null +++ b/Cuotas/Rutas/RutasIndividuales/leerSetCuotas.routes.js @@ -0,0 +1,67 @@ +//RF33 - LEER COUTA SET - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF33] +const express = require('express'); +const ruteador = express.Router(); +const controlador = require('@altertex/cuota/ctrl/leerSetCuotas.controller'); +const revisarApiKey = require('@altertex/util/inter/revisarApiKey'); +const autorizarToken = require('@altertex/util/inter/autorizarToken'); +const validarYSanitizar = require('@altertex/util/inter/validarYSanitizar'); +const verificarPermisos = require('@altertex/util/inter/verificarPermisos'); +const PERMISOS = require('@altertex/util/const/permisos'); +const RUTAS = require('@altertex/util/const/rutas'); + +/** + * @swagger + * /api/cuotas/leer-set-cuotas: + * post: + * summary: Leer set de cuotas + * description: Obtiene el set de cuotas según los parámetros enviados. + * tags: + * - Cuotas + * security: + * - ApiKeyAuth: [] + * - BearerAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * idSet: + * type: string + * description: ID del set de cuotas a consultar + * required: + * - idSet + * responses: + * '200': + * description: Set de cuotas obtenido correctamente + * content: + * application/json: + * schema: + * type: object + * properties: + * cuotas: + * type: array + * items: + * type: object + * # Define los campos de cada cuota aquí + * '401': + * description: No autorizado (API Key o Token inválido) + * '403': + * description: Permisos insuficientes + * '400': + * description: Error de validación o parámetros incorrectos + * '500': + * description: Error interno del servidor + */ + +ruteador.post( + RUTAS.CUOTAS.LEER_SET_CUOTAS, + validarYSanitizar, + revisarApiKey(), + autorizarToken, + verificarPermisos([PERMISOS.LEER_SET_CUOTAS]), + controlador.leerSetCuotas +); + +module.exports = ruteador; diff --git a/Cuotas/Rutas/indexCuotas.routes.js b/Cuotas/Rutas/indexCuotas.routes.js index f8e91d1f..ea69bba4 100644 --- a/Cuotas/Rutas/indexCuotas.routes.js +++ b/Cuotas/Rutas/indexCuotas.routes.js @@ -1,17 +1,17 @@ -const express = require("express"); +const express = require('express'); const ruteador = express.Router(); -const rutaCrearCuota = require("@altertex/cuota/rutasInd/crearCuota.routes"); -const rutaObtenerOpcionesCuota = require("@altertex/cuota/rutasInd/obtenerOpcionesCuotas.routes"); -const rutasConsultarListaCuotas = require("@altertex/cuota/rutasInd/consultarCuotas.routes"); +const rutaCrearCuota = require('@altertex/cuota/rutasInd/crearCuota.routes'); +const rutaObtenerOpcionesCuota = require('@altertex/cuota/rutasInd/obtenerOpcionesCuotas.routes'); +const rutasConsultarListaCuotas = require('@altertex/cuota/rutasInd/consultarCuotas.routes'); const rutaEliminarSetCuotas = require('@altertex/cuota/rutasInd/eliminarSetCuotas.routes'); +const rutaLeerCuota = require('@altertex/cuota/rutasInd/leerSetCuotas.routes'); - -const RUTAS = require("@altertex/util/const/rutas"); +const RUTAS = require('@altertex/util/const/rutas'); ruteador.use(RUTAS.CUOTAS.BASE, rutaCrearCuota); ruteador.use(RUTAS.CUOTAS.BASE, rutaObtenerOpcionesCuota); ruteador.use(RUTAS.CUOTAS.BASE, rutasConsultarListaCuotas); -ruteador.use(RUTAS.CUOTAS.BASE, rutaEliminarSetCuotas) - +ruteador.use(RUTAS.CUOTAS.BASE, rutaEliminarSetCuotas); +ruteador.use(RUTAS.CUOTAS.BASE, rutaLeerCuota); module.exports = ruteador; diff --git a/Utilidades/Constantes/consultasCuotas.js b/Utilidades/Constantes/consultasCuotas.js index be725c22..c8aae885 100644 --- a/Utilidades/Constantes/consultasCuotas.js +++ b/Utilidades/Constantes/consultasCuotas.js @@ -40,6 +40,15 @@ module.exports = { ELIMINAR_CUOTA_SET: ` DELETE FROM cuota_set WHERE idCuotaSet = ?; `, - - + LEER_CUOTA_SET: ` + SELECT + cs.idCuotaSet, + cs.nombre, + cs.descripcion, + cs.periodoRenovacion, + cs.renovacionHabilitada, + cs.ultimaActualizacion, + FROM cuota_set cs + WHERE cs.idCuotaSet = ?; + `, }; diff --git a/Utilidades/Constantes/rutas.js b/Utilidades/Constantes/rutas.js index ce4a8694..00717dba 100644 --- a/Utilidades/Constantes/rutas.js +++ b/Utilidades/Constantes/rutas.js @@ -63,6 +63,7 @@ module.exports = { OPCIONES: '/obtener-opciones', CONSULTAR_LISTA: '/consultar-lista', ELIMINAR_SET_CUOTAS: '/eliminar-set-cuotas', + LEER_SET_CUOTAS: '/leer-set-cuotas', }, ROLES: { BASE: '/roles', From f459ad7d15eac834c57f0397788e6fc8ee0677bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Antonio=20Ben=C3=ADtez=20De=20La=20Portilla?= Date: Sun, 11 May 2025 04:03:32 -0600 Subject: [PATCH 002/116] feat: Refactor leerSetCuotas to handle set of cuotas and update related messages and queries --- .../Controladores/leerSetCuotas.controller.js | 25 ++++++------ .../Repositorios/leerSetCuotasRepositorio.js | 12 ++---- .../RutasIndividuales/leerSetCuotas.routes.js | 2 +- Utilidades/Constantes/consultasCuotas.js | 2 +- Utilidades/Constantes/mensajesCuotas.js | 38 ++++++++++--------- 5 files changed, 38 insertions(+), 41 deletions(-) diff --git a/Cuotas/Controladores/leerSetCuotas.controller.js b/Cuotas/Controladores/leerSetCuotas.controller.js index b9fc419f..768f72a1 100644 --- a/Cuotas/Controladores/leerSetCuotas.controller.js +++ b/Cuotas/Controladores/leerSetCuotas.controller.js @@ -4,16 +4,15 @@ const MENSAJES_CUOTAS = require('@altertex/util/const/mensajesCuotas'); /** * Lee un conjunto de cuotas desde la base de datos utilizando su ID. * - * Valida el parámetro `idCuota` y obtiene la información de las cuotas a través del repositorio. - * Si las cuotas no son encontradas o el parámetro es inválido, retorna un error. + * Valida el parámetro `idSetCuota` y obtiene la información del set de cuotas a través del repositorio. + * Si el set de cuotas no es encontrado o el parámetro es inválido, retorna un error. * - * @param {Express.Request} req - La solicitud HTTP que contiene el `idCuota` en el cuerpo. + * @param {Express.Request} req - La solicitud HTTP que contiene el `idSetCuota` en el cuerpo. * @param {Express.Response} res - La respuesta HTTP para enviar el resultado al cliente. - * @returns {Promise} Responde con las cuotas encontradas o un mensaje de error. + * @returns {Promise} Responde con el set de cuotas encontrado o un mensaje de error. * - * @see [RF33 Leer set cuotas](https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF38) + * @see [RF33 Leer set cuotas](https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF33) */ - exports.leerSetCuotas = async (req, res) => { const idSetCuota = parseInt(req.body.idSetCuota); @@ -24,22 +23,22 @@ exports.leerSetCuotas = async (req, res) => { } try { - const setCuota = await repositorio.obtenerCuotasPorId(idSetCuota); + const setCuota = await repositorio.obtenerSetCuotaPorId(idSetCuota); if (!setCuota) { return res - .status(MENSAJES_CUOTAS.CUOTAS_NO_ENCONTRADAS.codigo) - .json({ mensaje: MENSAJES_CUOTAS.CUOTAS_NO_ENCONTRADAS.mensaje }); + .status(MENSAJES_CUOTAS.SET_CUOTA_NO_ENCONTRADO.codigo) + .json({ mensaje: MENSAJES_CUOTAS.SET_CUOTA_NO_ENCONTRADO.mensaje }); } - return res.status(MENSAJES_CUOTAS.CUOTAS_OBTENIDAS.codigo).json({ - mensaje: MENSAJES_CUOTAS.CUOTAS_OBTENIDAS.mensaje, + return res.status(MENSAJES_CUOTAS.CONSULTA_EXITOSA.codigo).json({ + mensaje: MENSAJES_CUOTAS.CONSULTA_EXITOSA.mensaje, setCuota, }); } catch (error) { console.error('Error al consultar Set cuotas:', error); return res - .status(MENSAJES_CUOTAS.ERROR_OBTENER_CUOTAS.codigo) - .json({ mensaje: MENSAJES_CUOTAS.ERROR_OBTENER_CUOTAS.mensaje }); + .status(MENSAJES_CUOTAS.ERROR_OBTENER_SET_CUOTA.codigo) + .json({ mensaje: MENSAJES_CUOTAS.ERROR_OBTENER_SET_CUOTA.mensaje }); } }; diff --git a/Cuotas/Datos/Repositorios/leerSetCuotasRepositorio.js b/Cuotas/Datos/Repositorios/leerSetCuotasRepositorio.js index e8d4e3c4..59c03296 100644 --- a/Cuotas/Datos/Repositorios/leerSetCuotasRepositorio.js +++ b/Cuotas/Datos/Repositorios/leerSetCuotasRepositorio.js @@ -6,13 +6,10 @@ const CONSULTAS_CUOTAS = require('@altertex/util/const/consultasCuotas'); * * Ejecuta una consulta SQL y retorna el primer conjunto de cuotas encontrado o `null` si no existe. * - * @param {number|string} idSetCuota de cuota a buscar. + * @param {number} idSetCuota - ID del set de cuotas a buscar. * @returns {Promise} El conjunto de cuotas encontrado o `null` si no existe. * @throws {Error} Si ocurre un error al ejecutar la consulta. - * - * @see [RF33 Leer conjunto de cuotas](https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF38) */ - exports.obtenerSetCuotaPorId = async (idSetCuota) => { const query = CONSULTAS_CUOTAS.LEER_CUOTA_SET; @@ -21,13 +18,12 @@ exports.obtenerSetCuotaPorId = async (idSetCuota) => { if (resultado.length === 0) return null; const setCuota = { - idSetCuota: resultado[0].idSetCuota, + idSetCuota: resultado[0].idCuotaSet, nombre: resultado[0].nombre, descripcion: resultado[0].descripcion, - puntos: resultado[0].puntos, - multiplicador: resultado[0].multiplicador, periodoRenovacion: resultado[0].periodoRenovacion, - renovacion: resultado[0].renovacion, + renovacionHabilitada: resultado[0].renovacionHabilitada, + ultimaActualizacion: resultado[0].ultimaActualizacion, }; return setCuota; diff --git a/Cuotas/Rutas/RutasIndividuales/leerSetCuotas.routes.js b/Cuotas/Rutas/RutasIndividuales/leerSetCuotas.routes.js index db6f4786..e5481465 100644 --- a/Cuotas/Rutas/RutasIndividuales/leerSetCuotas.routes.js +++ b/Cuotas/Rutas/RutasIndividuales/leerSetCuotas.routes.js @@ -60,7 +60,7 @@ ruteador.post( validarYSanitizar, revisarApiKey(), autorizarToken, - verificarPermisos([PERMISOS.LEER_SET_CUOTAS]), + verificarPermisos(PERMISOS.LEER_SET_CUOTAS), controlador.leerSetCuotas ); diff --git a/Utilidades/Constantes/consultasCuotas.js b/Utilidades/Constantes/consultasCuotas.js index c8aae885..cf7f0173 100644 --- a/Utilidades/Constantes/consultasCuotas.js +++ b/Utilidades/Constantes/consultasCuotas.js @@ -47,7 +47,7 @@ module.exports = { cs.descripcion, cs.periodoRenovacion, cs.renovacionHabilitada, - cs.ultimaActualizacion, + cs.ultimaActualizacion FROM cuota_set cs WHERE cs.idCuotaSet = ?; `, diff --git a/Utilidades/Constantes/mensajesCuotas.js b/Utilidades/Constantes/mensajesCuotas.js index 9ee05ba0..a730325f 100644 --- a/Utilidades/Constantes/mensajesCuotas.js +++ b/Utilidades/Constantes/mensajesCuotas.js @@ -2,45 +2,43 @@ module.exports = { // crearCuota - FORMATO_INVALIDO: "Formato de cuota set inválido", - CREACION_EXITOSA: "Cuota set creado exitosamente", - ERROR_CREACION: "Error creando cuota set", + FORMATO_INVALIDO: 'Formato de cuota set inválido', + CREACION_EXITOSA: 'Cuota set creado exitosamente', + ERROR_CREACION: 'Error creando cuota set', // obtenerOpcionesCuotas - FALTA_ID_CLIENTE: "No hay idCliente", - OPCIONES_OBTENIDAS: "Opciones producto para cuota", - ERROR_OBTENIENDO_OPCIONES: "Error obteniendo opciones", + FALTA_ID_CLIENTE: 'No hay idCliente', + OPCIONES_OBTENIDAS: 'Opciones producto para cuota', + ERROR_OBTENIENDO_OPCIONES: 'Error obteniendo opciones', // validarCuotaSet NOMBRE_REQUERIDO: 'El campo "nombre" es obligatorio.', - PRODUCTOS_REQUERIDOS: "Debes enviar al menos un producto con su límite.", - ID_PRODUCTO_INVALIDO: (pos) => - `El producto en la posición ${pos} no tiene un idProducto válido.`, + PRODUCTOS_REQUERIDOS: 'Debes enviar al menos un producto con su límite.', + ID_PRODUCTO_INVALIDO: (pos) => `El producto en la posición ${pos} no tiene un idProducto válido.`, LIMITE_INVALIDO: (id) => `El producto "${id}" tiene un "limite" inválido.`, - LIMITE_ACTUAL_INVALIDO: (id) => - `El producto "${id}" tiene un "limiteActual" inválido.`, + LIMITE_ACTUAL_INVALIDO: (id) => `El producto "${id}" tiene un "limiteActual" inválido.`, - // consultarListaCuotas + // consultarListaCuotas CONSULTA_EXITOSA: { codigo: 200, - mensaje: "Lista de sets de cuotas obtenida exitosamente.", + mensaje: 'Lista de sets de cuotas obtenida exitosamente.', }, SIN_RESULTADOS: { codigo: 204, - mensaje: "No se encontraron sets de cuotas registrados para el cliente.", + mensaje: 'No se encontraron sets de cuotas registrados para el cliente.', }, PARAMETROS_INVALIDOS: { codigo: 400, - mensaje: "Falta el ID del cliente para realizar la consulta.", + mensaje: 'Falta el ID del cliente para realizar la consulta.', }, ERROR_CONSULTAR_CUOTAS: { codigo: 500, - mensaje: "Error al consultar los sets de cuotas.", + mensaje: 'Error al consultar los sets de cuotas.', }, - + SET_CUOTA_NO_ENCONTRADO: { codigo: 404, mensaje: 'Set de cuotas no encontrado.', @@ -55,5 +53,9 @@ module.exports = { codigo: 500, mensaje: 'Ocurrió un error al eliminar el set de productos.', }, - + + ERROR_OBTENER_SET_CUOTA: { + codigo: 500, + mensaje: 'Error interno al obtener el set de cuotas.', + }, }; From 3d172c5d47503fb651175ffc80cc60e571132b50 Mon Sep 17 00:00:00 2001 From: Diego Alfaro Pinto Date: Mon, 12 May 2025 18:14:49 -0600 Subject: [PATCH 003/116] feat: comenzar actualizar grupo empleados --- .../actualizarGrupoEmpleado.controller.js | 1 + .../actualizarGrupoEmpleados.routes.js | 20 +++++++++++++++++++ Empleados/Rutas/indexEmpleados.routes.js | 3 +++ Utilidades/Constantes/rutas.js | 1 + 4 files changed, 25 insertions(+) create mode 100644 Empleados/Controladores/actualizarGrupoEmpleado.controller.js create mode 100644 Empleados/Rutas/RutasIndividuales/actualizarGrupoEmpleados.routes.js diff --git a/Empleados/Controladores/actualizarGrupoEmpleado.controller.js b/Empleados/Controladores/actualizarGrupoEmpleado.controller.js new file mode 100644 index 00000000..8d42684f --- /dev/null +++ b/Empleados/Controladores/actualizarGrupoEmpleado.controller.js @@ -0,0 +1 @@ +exports.actualizarGrupoEmpleados = async (req, res) => {}; diff --git a/Empleados/Rutas/RutasIndividuales/actualizarGrupoEmpleados.routes.js b/Empleados/Rutas/RutasIndividuales/actualizarGrupoEmpleados.routes.js new file mode 100644 index 00000000..f6ccc89e --- /dev/null +++ b/Empleados/Rutas/RutasIndividuales/actualizarGrupoEmpleados.routes.js @@ -0,0 +1,20 @@ +const express = require('express'); +const ruteador = express.Router(); +const RUTAS = require('@altertex/util/const/rutas'); +const PERMISOS = require('@altertex/util/const/permisos'); +const validarYSanitizar = require('@altertex/util/inter/validarYSanitizar'); +const revisarApiKey = require('@altertex/util/inter/revisarApiKey'); +const autorizarToken = require('@altertex/util/inter/autorizarToken'); +const verificarPermisos = require('@altertex/util/inter/verificarPermisos'); +const controlador = require('@altertex/emp/ctrl/actualizarGrupoEmpleado.controller'); + +ruteador.put( + RUTAS.EMPLEADOS.ACTUALIZAR_GRUPO_EMPLEADO, + revisarApiKey(), + autorizarToken, + verificarPermisos(PERMISOS.ACTUALIZAR_GRUPO_EMPLEADOS), + validarYSanitizar, + controlador.actualizarGrupoEmpleados +); + +module.exports = ruteador; diff --git a/Empleados/Rutas/indexEmpleados.routes.js b/Empleados/Rutas/indexEmpleados.routes.js index 3b90ed16..4151e849 100644 --- a/Empleados/Rutas/indexEmpleados.routes.js +++ b/Empleados/Rutas/indexEmpleados.routes.js @@ -6,6 +6,7 @@ const rutasEliminarGrupo = require('@altertex/emp/rutasInd/eliminarGrupoEmpleado const rutasEliminarEmpleado = require('@altertex/emp/rutasInd/eliminarEmpleado.routes'); const rutasLeerGrupoEmpleados = require('@altertex/emp/rutasInd/leerGrupoEmpleados.routes'); const rutasCrearGrupo = require('@altertex/emp/rutasInd/crearGrupoEmpleados.routes'); +const rutasActualizarEmpleado = require('@altertex/emp/rutasInd/actualizarGrupoEmpleados.routes'); const RUTAS = require('@altertex/util/const/rutas'); @@ -22,4 +23,6 @@ ruteador.use(RUTAS.EMPLEADOS.BASE, rutasLeerGrupoEmpleados); //RF21 - Crear Grupo de Empleados - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF21 ruteador.use(RUTAS.EMPLEADOS.BASE, rutasCrearGrupo); +ruteador.use(RUTAS.EMPLEADOS.BASE, rutasActualizarEmpleado); + module.exports = ruteador; diff --git a/Utilidades/Constantes/rutas.js b/Utilidades/Constantes/rutas.js index b63e8d7a..618596d0 100644 --- a/Utilidades/Constantes/rutas.js +++ b/Utilidades/Constantes/rutas.js @@ -59,6 +59,7 @@ module.exports = { ELIMINAR_EMPLEADO: '/eliminar', LEER_GRUPO: '/leer-grupo', CREAR_GRUPO: '/crear-grupo', + ACTUALIZAR_GRUPO_EMPLEADO: '/actualizar-grupo', }, CUOTAS: { BASE: '/cuotas', From b93b2734202f2488e55c7d643cbbc2edadec0c2f Mon Sep 17 00:00:00 2001 From: Diego Alfaro Pinto Date: Tue, 13 May 2025 01:25:51 -0600 Subject: [PATCH 004/116] feat: agregar actualizacion a empleados y nombre y descripcion, faltan sets de productos --- .../actualizarGrupoEmpleado.controller.js | 21 +++++++++- .../repositorioActualizarGrupo.js | 42 +++++++++++++++++++ .../Constantes/consultasGrupoEmpleados.js | 22 ++++++++++ .../Constantes/mensajesGrupoEmpleados.js | 16 +++++++ 4 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 Empleados/Datos/Repositorios/repositorioActualizarGrupo.js diff --git a/Empleados/Controladores/actualizarGrupoEmpleado.controller.js b/Empleados/Controladores/actualizarGrupoEmpleado.controller.js index 8d42684f..08e68b39 100644 --- a/Empleados/Controladores/actualizarGrupoEmpleado.controller.js +++ b/Empleados/Controladores/actualizarGrupoEmpleado.controller.js @@ -1 +1,20 @@ -exports.actualizarGrupoEmpleados = async (req, res) => {}; +const MENSAJES = require('@altertex/util/const/mensajesGrupoEmpleados'); +const repositorio = require('@altertex/emp/repos/repositorioActualizarGrupo'); + +exports.actualizarGrupoEmpleados = async (req, res) => { + const datosActualizacion = req.body; + if (!datosActualizacion || Object.keys(datosActualizacion).length === 0) { + return res + .status(MENSAJES.FORMATO_INVALIDO_DATOS.codigo) + .json({ mensaje: MENSAJES.FORMATO_INVALIDO_DATOS.mensaje }); + } + try { + await repositorio.actualizarGrupoEmpleados(datosActualizacion); + + return res + .status(MENSAJES.GRUPO_ACTUALIZADO.codigo) + .json({ mensaje: MENSAJES.GRUPO_ACTUALIZADO.mensaje }); + } catch (error) { + return res.status(MENSAJES.ERROR_ACTUALIZAR_GRUPOS.codigo).json({ mensaje: error.message }); + } +}; diff --git a/Empleados/Datos/Repositorios/repositorioActualizarGrupo.js b/Empleados/Datos/Repositorios/repositorioActualizarGrupo.js new file mode 100644 index 00000000..a33b4b7e --- /dev/null +++ b/Empleados/Datos/Repositorios/repositorioActualizarGrupo.js @@ -0,0 +1,42 @@ +const CONSULTAS = require('@altertex/util/const/consultasGrupoEmpleados'); +const correrQuery = require('@altertex/util/ser/correrQuery'); +const MENSAJES = require('@altertex/util/const/mensajesGrupoEmpleados'); + +exports.actualizarGrupoEmpleados = async (datosActualizacion) => { + const { idGrupoEmpleado, nombre, descripcion, empleados, setsDeProductos } = datosActualizacion; + + try { + await correrQuery(CONSULTAS.ACTUALIZAR_GRUPO_EMPLEADOS_NOMBRE_DESCRIPCION, [ + nombre, + descripcion, + idGrupoEmpleado, + nombre, + descripcion, + ]); + + if (empleados.length > 0) { + const empleadosSTR = empleados.join(', '); + const valores = empleados + .map((empleadoId) => `(${empleadoId}, ${idGrupoEmpleado})`) + .join(', '); + + const resultadoVerificacion = await correrQuery( + CONSULTAS.VERIFICAR_EMPLEADOS_CLIENTE.replace('__EMPLEADOS__', empleadosSTR), + [idGrupoEmpleado] + ); + + if (resultadoVerificacion[0].validos !== empleados.length) { + throw new Error(MENSAJES.ERROR_VERIFICACION_CLIENTE_EMPLEADO.mensaje); + } + await correrQuery( + CONSULTAS.ELIMINAR_EMPLEADOS_DE_GRUPO_BASE.replace('__ID__', idGrupoEmpleado).replace( + '__EMPLEADOS__', + empleadosSTR + ) + ); + await correrQuery(CONSULTAS.AGREGAR_EMPLEADOS_NUEVOS_BASE.replace('__VALORES__', valores)); + } + } catch (error) { + throw new Error(error); + } +}; diff --git a/Utilidades/Constantes/consultasGrupoEmpleados.js b/Utilidades/Constantes/consultasGrupoEmpleados.js index 5cade6c1..f4e908a4 100644 --- a/Utilidades/Constantes/consultasGrupoEmpleados.js +++ b/Utilidades/Constantes/consultasGrupoEmpleados.js @@ -40,4 +40,26 @@ module.exports = { GROUP BY ge.idGrupo ORDER BY ge.idGrupo; `, + ACTUALIZAR_GRUPO_EMPLEADOS_NOMBRE_DESCRIPCION: ` + UPDATE grupo_empleado + SET nombre = ?, descripcion = ? + WHERE idGrupo = ? + AND (nombre != ? OR descripcion != ?); + `, + ELIMINAR_EMPLEADOS_DE_GRUPO_BASE: ` + DELETE FROM empleado_grupo + WHERE idGrupo = __ID__ + AND idEmpleado NOT IN (__EMPLEADOS__); + `, + AGREGAR_EMPLEADOS_NUEVOS_BASE: ` + INSERT IGNORE INTO empleado_grupo (idEmpleado, idGrupo) + VALUES __VALORES__; + `, + VERIFICAR_EMPLEADOS_CLIENTE: ` + SELECT COUNT(*) AS validos + FROM empleado e + JOIN grupo_empleado g ON g.idGrupo = ? + WHERE e.idEmpleado IN (__EMPLEADOS__) + AND e.idCliente = g.idCliente + `, }; diff --git a/Utilidades/Constantes/mensajesGrupoEmpleados.js b/Utilidades/Constantes/mensajesGrupoEmpleados.js index b1530e8b..a731b270 100644 --- a/Utilidades/Constantes/mensajesGrupoEmpleados.js +++ b/Utilidades/Constantes/mensajesGrupoEmpleados.js @@ -51,4 +51,20 @@ module.exports = { codigo: 500, mensaje: 'Ocurrió un error al obtener los datos del grupo de empleados.', }, + GRUPO_ACTUALIZADO: { + codigo: 200, + mensaje: 'Se actualizo correctamente el grupo de empleados.', + }, + FORMATO_INVALIDO_DATOS: { + codigo: 400, + mensaje: 'No se obtuvieron los datos correctamente.', + }, + ERROR_ACTUALIZAR_GRUPOS: { + codigo: 400, + mensaje: 'Error actualizando el grupo de empleados.s', + }, + ERROR_VERIFICACION_CLIENTE_EMPLEADO: { + codigo: 400, + mensaje: 'Algunos empleados no pertenecen al mismo cliente que el grupo.', + }, }; From 744fd09b151cfc839a7cc6f56202177b7ceea1d6 Mon Sep 17 00:00:00 2001 From: Diego Alfaro Pinto Date: Tue, 13 May 2025 02:51:00 -0600 Subject: [PATCH 005/116] feat: agregar funcionalidad para que se actualicen los sets de productos, y que se muestre la ruta en swagger --- Configuracion/rutasSwagger.js | 1 + .../actualizarGrupoEmpleado.controller.js | 19 +++ .../repositorioActualizarGrupo.js | 46 +++++++ .../actualizarGrupoEmpleados.routes.js | 98 +++++++++++++ Empleados/Rutas/indexEmpleados.routes.js | 2 +- .../consultarEvento.routes.js | 129 +++++++++--------- .../Constantes/consultasGrupoEmpleados.js | 18 +++ .../Constantes/mensajesGrupoEmpleados.js | 4 + 8 files changed, 252 insertions(+), 65 deletions(-) diff --git a/Configuracion/rutasSwagger.js b/Configuracion/rutasSwagger.js index 5cda3636..3a3eea8c 100644 --- a/Configuracion/rutasSwagger.js +++ b/Configuracion/rutasSwagger.js @@ -23,6 +23,7 @@ module.exports = [ './Empleados/Rutas/RutasIndividuales/eliminarEmpleado.routes.js', './Empleados/Rutas/RutasIndividuales/eliminarGrupoEmpleados.routes.js', './Empleados/Rutas/RutasIndividuales/leerGrupoEmpleado.routes.js', + './Empleados/Rutas/RutasIndividuales/actualizarGrupoEmpleados.routes.js', './Eventos/Rutas/RutasIndividuales/consultarEvento.routes.js', './Eventos/Rutas/RutasIndividuales/consultarListaEventos.routes.js', diff --git a/Empleados/Controladores/actualizarGrupoEmpleado.controller.js b/Empleados/Controladores/actualizarGrupoEmpleado.controller.js index 08e68b39..3687ffcc 100644 --- a/Empleados/Controladores/actualizarGrupoEmpleado.controller.js +++ b/Empleados/Controladores/actualizarGrupoEmpleado.controller.js @@ -1,6 +1,25 @@ const MENSAJES = require('@altertex/util/const/mensajesGrupoEmpleados'); const repositorio = require('@altertex/emp/repos/repositorioActualizarGrupo'); +// RF[24] Actualiza grupo empleado - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF24] + +/** + * Controlador HTTP para actualizar un grupo de empleados. + * + * Valida que el cuerpo de la solicitud contenga los datos necesarios, + * y delega la lógica de actualización al repositorio correspondiente. + * + * Respuestas posibles: + * - 400 si faltan datos en el cuerpo de la solicitud. + * - 200 si el grupo se actualiza correctamente. + * - 500 si ocurre un error en el proceso de actualización. + * + * @async + * @function actualizarGrupoEmpleados + * @param {Express.Request} req - Objeto de solicitud HTTP de Express. + * @param {Express.Response} res - Objeto de respuesta HTTP de Express. + * @returns {Promise} La respuesta HTTP con el estado y mensaje correspondiente. + */ exports.actualizarGrupoEmpleados = async (req, res) => { const datosActualizacion = req.body; if (!datosActualizacion || Object.keys(datosActualizacion).length === 0) { diff --git a/Empleados/Datos/Repositorios/repositorioActualizarGrupo.js b/Empleados/Datos/Repositorios/repositorioActualizarGrupo.js index a33b4b7e..0b9960bc 100644 --- a/Empleados/Datos/Repositorios/repositorioActualizarGrupo.js +++ b/Empleados/Datos/Repositorios/repositorioActualizarGrupo.js @@ -1,7 +1,30 @@ const CONSULTAS = require('@altertex/util/const/consultasGrupoEmpleados'); const correrQuery = require('@altertex/util/ser/correrQuery'); const MENSAJES = require('@altertex/util/const/mensajesGrupoEmpleados'); +// RF[24] Actualiza grupo empleado - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF24] +/** + * Actualiza un grupo de empleados en la base de datos, incluyendo su nombre, + * descripción, empleados asociados y sets de productos relacionados. + * + * Esta función realiza: + * - La actualización del nombre y descripción del grupo. + * - La verificación y reemplazo de los empleados asociados al grupo. + * - La verificación y reemplazo de los sets de productos asociados al grupo. + * + * En caso de que los empleados o sets proporcionados no pertenezcan al mismo cliente, + * se lanza un error. + * + * @async + * @function actualizarGrupoEmpleados + * @param {object} datosActualizacion - Datos necesarios para actualizar el grupo. + * @param {number} datosActualizacion.idGrupoEmpleado - ID del grupo de empleados a actualizar. + * @param {string} datosActualizacion.nombre - Nuevo nombre del grupo. + * @param {string} datosActualizacion.descripcion - Nueva descripción del grupo. + * @param {number[]} datosActualizacion.empleados - Lista de IDs de empleados a asociar al grupo. + * @param {number[]} datosActualizacion.setsDeProductos - Lista de IDs de sets de productos a asociar al grupo. + * @throws {Error} Si ocurre algún error durante la actualización o verificación de empleados/sets. + */ exports.actualizarGrupoEmpleados = async (datosActualizacion) => { const { idGrupoEmpleado, nombre, descripcion, empleados, setsDeProductos } = datosActualizacion; @@ -36,6 +59,29 @@ exports.actualizarGrupoEmpleados = async (datosActualizacion) => { ); await correrQuery(CONSULTAS.AGREGAR_EMPLEADOS_NUEVOS_BASE.replace('__VALORES__', valores)); } + + if (setsDeProductos.length > 0) { + const setsSTR = setsDeProductos.join(', '); + const valores = setsDeProductos.map((setId) => `(${setId}, ${idGrupoEmpleado})`).join(', '); + + const resultadoVerificacion = await correrQuery( + CONSULTAS.VERIFICAR_SETS_CLIENTE.replace('__SETS__', setsSTR), + [idGrupoEmpleado] + ); + + if (resultadoVerificacion[0].validos !== setsDeProductos.length) { + throw new Error(MENSAJES.ERROR_VERIFICACION_CLIENTE_SET.mensaje); + } + + await correrQuery( + CONSULTAS.ELIMINAR_SETS_DE_GRUPO_BASE.replace('__ID__', idGrupoEmpleado).replace( + '__SETS__', + setsSTR + ) + ); + + await correrQuery(CONSULTAS.AGREGAR_SETS_NUEVOS_BASE.replace('__VALORES__', valores)); + } } catch (error) { throw new Error(error); } diff --git a/Empleados/Rutas/RutasIndividuales/actualizarGrupoEmpleados.routes.js b/Empleados/Rutas/RutasIndividuales/actualizarGrupoEmpleados.routes.js index f6ccc89e..4a1dd3c1 100644 --- a/Empleados/Rutas/RutasIndividuales/actualizarGrupoEmpleados.routes.js +++ b/Empleados/Rutas/RutasIndividuales/actualizarGrupoEmpleados.routes.js @@ -7,7 +7,105 @@ const revisarApiKey = require('@altertex/util/inter/revisarApiKey'); const autorizarToken = require('@altertex/util/inter/autorizarToken'); const verificarPermisos = require('@altertex/util/inter/verificarPermisos'); const controlador = require('@altertex/emp/ctrl/actualizarGrupoEmpleado.controller'); +// RF[24] Actualiza grupo empleado - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF24] +/** + * @swagger + * /api/empleados/actualizar-grupos: + * put: + * summary: Actualiza un grupo de empleados + * description: Actualiza el nombre, la descripción, los empleados y los sets de productos asociados a un grupo de empleados existente. + * tags: + * - Grupos de Empleados + * security: + * - ApiKeyAuth: [] + * - BearerAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - idGrupoEmpleado + * - nombre + * - descripcion + * - empleados + * - setsDeProductos + * properties: + * idGrupoEmpleado: + * type: integer + * example: 1 + * nombre: + * type: string + * example: "Grupo Administrativo" + * descripcion: + * type: string + * example: "Grupo para empleados administrativos" + * empleados: + * type: array + * items: + * type: integer + * example: [101, 102, 103] + * setsDeProductos: + * type: array + * items: + * type: integer + * example: [201, 202] + * responses: + * 200: + * description: Se actualizó correctamente el grupo de empleados. + * content: + * application/json: + * schema: + * type: object + * properties: + * mensaje: + * type: string + * example: Se actualizo correctamente el grupo de empleados. + * 400: + * description: Error de validación o datos inválidos. + * content: + * application/json: + * schema: + * type: object + * properties: + * mensaje: + * type: string + * examples: + * Formato inválido: + * value: + * mensaje: No se obtuvieron los datos correctamente. + * Verificación de empleados fallida: + * value: + * mensaje: Algunos empleados no pertenecen al mismo cliente que el grupo. + * Verificación de sets fallida: + * value: + * mensaje: Algunos sets de productos no pertenecen al cliente de este grupo. + * Actualización fallida: + * value: + * mensaje: Error actualizando el grupo de empleados. + * 403: + * description: No tiene permisos para realizar esta acción. + * content: + * application/json: + * schema: + * type: object + * properties: + * mensaje: + * type: string + * example: No tiene permiso para consultar grupos de empleados de este cliente. + * 500: + * description: Error interno del servidor. + * content: + * application/json: + * schema: + * type: object + * properties: + * mensaje: + * type: string + * example: Ocurrió un error al actualizar el grupo de empleados. + */ ruteador.put( RUTAS.EMPLEADOS.ACTUALIZAR_GRUPO_EMPLEADO, revisarApiKey(), diff --git a/Empleados/Rutas/indexEmpleados.routes.js b/Empleados/Rutas/indexEmpleados.routes.js index 4151e849..de9380a5 100644 --- a/Empleados/Rutas/indexEmpleados.routes.js +++ b/Empleados/Rutas/indexEmpleados.routes.js @@ -22,7 +22,7 @@ ruteador.use(RUTAS.EMPLEADOS.BASE, rutasEliminarEmpleado); ruteador.use(RUTAS.EMPLEADOS.BASE, rutasLeerGrupoEmpleados); //RF21 - Crear Grupo de Empleados - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF21 ruteador.use(RUTAS.EMPLEADOS.BASE, rutasCrearGrupo); - +// RF[24] Actualiza grupo empleado - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF24] ruteador.use(RUTAS.EMPLEADOS.BASE, rutasActualizarEmpleado); module.exports = ruteador; diff --git a/Eventos/Rutas/RutasIndividuales/consultarEvento.routes.js b/Eventos/Rutas/RutasIndividuales/consultarEvento.routes.js index 3fa92d87..f8e82800 100644 --- a/Eventos/Rutas/RutasIndividuales/consultarEvento.routes.js +++ b/Eventos/Rutas/RutasIndividuales/consultarEvento.routes.js @@ -17,76 +17,77 @@ const RUTAS = require('@altertex/util/const/rutas'); * summary: Muestra la información de un evento específico. * description: | * Este endpoint permite consultar los datos de un evento por su ID. - * tags: [Eventos] - * security: - * - ApiKeyAuth: [] - * requestBody: - * required: true + * tags: + * - Eventos + * security: + * - ApiKeyAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - idEvento + * properties: + * idEvento: + * type: integer + * example: 123 + * responses: + * 200: + * description: Evento encontrado exitosamente. * content: * application/json: * schema: * type: object * properties: - * idEvento: - * type: integer - * example: 123 - * required: - * - idEvento - * responses: - * 200: - * description: Evento encontrado exitosamente. - * content: - * application/json: - * schema: - * type: object - * properties: - * mensaje: - * type: string - * example: "Información del evento obtenida exitosamente." - * evento: - * type: object - * properties: - * idEvento: - * type: integer - * example: 123 - * nombre: - * type: string - * example: "Entrega Puntual" - * descripcion: - * type: string - * example: "Se otorgan puntos por cumplir con los tiempos de entrega en producción." - * puntos: - * type: integer - * example: 10 - * multiplicador: - * type: number - * example: 1.5 - * periodoRenovacion: - * type: string - * example: "Mensual" - * renovacion: - * type: boolean + * mensaje: + * type: string + * example: "Información del evento obtenida exitosamente." + * evento: + * type: object + * properties: + * idEvento: + * type: integer + * example: 123 + * nombre: + * type: string + * example: "Entrega Puntual" + * descripcion: + * type: string + * example: "Se otorgan puntos por cumplir con los tiempos de entrega en producción." + * puntos: + * type: integer + * example: 10 + * multiplicador: + * type: number + * example: 1.5 + * periodoRenovacion: + * type: string + * example: "Mensual" + * renovacion: + * type: boolean * example: true - * 404: - * description: Evento no encontrado. - * content: - * application/json: - * schema: - * type: object - * properties: - * mensaje: - * type: string - * example: "Evento no encontrado." - * 500: - * description: Error interno del servidor al leer el evento. - * content: - * application/json: - * schema: - * type: object - * properties: - * mensaje: - * type: string - * example: "Ocurrió un error al leer el evento." + * 404: + * description: Evento no encontrado. + * content: + * application/json: + * schema: + * type: object + * properties: + * mensaje: + * type: string + * example: "Evento no encontrado." + * 500: + * description: Error interno del servidor al leer el evento. + * content: + * application/json: + * schema: + * type: object + * properties: + * mensaje: + * type: string + * example: "Ocurrió un error al leer el evento." */ ruteador.post( RUTAS.EVENTOS.CONSULTAR_EVENTO, diff --git a/Utilidades/Constantes/consultasGrupoEmpleados.js b/Utilidades/Constantes/consultasGrupoEmpleados.js index f4e908a4..adb2d69c 100644 --- a/Utilidades/Constantes/consultasGrupoEmpleados.js +++ b/Utilidades/Constantes/consultasGrupoEmpleados.js @@ -62,4 +62,22 @@ module.exports = { WHERE e.idEmpleado IN (__EMPLEADOS__) AND e.idCliente = g.idCliente `, + VERIFICAR_SETS_CLIENTE: ` + SELECT COUNT(*) AS validos + FROM set_producto s + JOIN grupo_empleado g ON g.idGrupo = ? + WHERE s.idSetProducto IN (__SETS__) + AND s.idCliente = g.idCliente +`, + + ELIMINAR_SETS_DE_GRUPO_BASE: ` + DELETE FROM set_producto_grupo_empleado + WHERE idGrupo = __ID__ + AND idSetProducto NOT IN (__SETS__); +`, + + AGREGAR_SETS_NUEVOS_BASE: ` + INSERT IGNORE INTO set_producto_grupo_empleado (idSetProducto, idGrupo) + VALUES __VALORES__; +`, }; diff --git a/Utilidades/Constantes/mensajesGrupoEmpleados.js b/Utilidades/Constantes/mensajesGrupoEmpleados.js index a731b270..1e3dd0ef 100644 --- a/Utilidades/Constantes/mensajesGrupoEmpleados.js +++ b/Utilidades/Constantes/mensajesGrupoEmpleados.js @@ -67,4 +67,8 @@ module.exports = { codigo: 400, mensaje: 'Algunos empleados no pertenecen al mismo cliente que el grupo.', }, + ERROR_VERIFICACION_CLIENTE_SET: { + codigo: 400, + mensaje: 'Algunos sets de productos no pertenecen al cliente de este grupo.', + }, }; From 2c17567a416fb5b9330011a5a1f34a8d8a1edb9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Antonio=20Ben=C3=ADtez=20De=20La=20Portilla?= Date: Wed, 14 May 2025 14:24:42 -0600 Subject: [PATCH 006/116] =?UTF-8?q?Add:=20Nuevo=20query=20de=20LEER=5FCUOT?= =?UTF-8?q?A=5FSET=5FPRODUCTOS=20para=20obtener=20los=20productos=20y=20la?= =?UTF-8?q?s=20cuotas=20de=20esos=20productos=20en=20un=20set=20de=20cuota?= =?UTF-8?q?s=20espec=C3=ADficos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Utilidades/Constantes/consultasCuotas.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Utilidades/Constantes/consultasCuotas.js b/Utilidades/Constantes/consultasCuotas.js index cf7f0173..09a035c5 100644 --- a/Utilidades/Constantes/consultasCuotas.js +++ b/Utilidades/Constantes/consultasCuotas.js @@ -51,4 +51,16 @@ module.exports = { FROM cuota_set cs WHERE cs.idCuotaSet = ?; `, + + LEER_CUOTA_SET_PRODUCTOS: ` + SELECT + p.nombreComun, + csp.limite AS cuota_valor + FROM cuota_set cs + JOIN cuota_set_producto csp + ON cs.idCuotaSet = csp.idCuotaSet + JOIN producto p + ON p.idProducto = csp.idProducto + WHERE cs.idCuotaSet = ?; + `, }; From 91f67482d57926d0e73d3f79cb591cdca79fb355 Mon Sep 17 00:00:00 2001 From: max Date: Fri, 16 May 2025 12:56:47 -0600 Subject: [PATCH 007/116] merge develop --- .../eliminarCliente.controller.js | 24 +- .../consultarLista.controller.js | 6 - .../consultarListaGrupos.controller.js | 6 - .../importarEmpleados.controller.js | 11 +- .../Repositorios/repositorioEmpleados.js | 4 - .../repositorioGrupoDeEmpleados.js | 4 - .../importarEmpleados.routes.js | 2 + .../Controladores/crearProducto.controller.js | 205 ++++++++++++++++++ .../Repositorios/repositorioCrearOpcion.js | 33 +++ .../Repositorios/repositorioCrearProducto.js | 62 ++++++ .../Repositorios/repositorioCrearVariante.js | 32 +++ .../Repositorios/repositorioProductoImagen.js | 43 ++++ .../Repositorios/repositorioVarianteImagen.js | 42 ++++ .../consultarProductos.routes.js | 2 +- .../RutasIndividuales/crearProducto.routes.js | 116 ++++++++++ .../eliminarProducto.routes.js | 2 +- Productos/Rutas/indexProductos.routes.js | 11 +- .../consultarProveedores.controller.js | 51 +++++ .../crearProveedor.controller.js | 50 +++++ .../repositorioConsultarProveedores.js | 25 +++ .../Repositorios/repositorioCrearProvedor.js | 46 ++++ .../consultarProveedores.routes.js | 126 +++++++++++ .../crearProveedor.routes.js | 125 +++++++++++ Proveedores/Rutas/indexProveedores.routes.js | 13 ++ .../Datos/Repositorios/repositorioCrearRol.js | 6 +- Utilidades/Constantes/consultasImagenes.js | 6 + Utilidades/Constantes/consultasOpciones.js | 6 + Utilidades/Constantes/consultasProductos.js | 18 +- Utilidades/Constantes/consultasProveedores.js | 10 + Utilidades/Constantes/consultasUsuarios.js | 8 +- Utilidades/Constantes/consultasVariantes.js | 10 + Utilidades/Constantes/mensajesProductos.js | 47 +++- Utilidades/Constantes/mensajesProveedores.js | 53 +++++ Utilidades/Constantes/rutas.js | 8 + .../Validaciones/validarOpciones.js | 86 ++++++++ .../Validaciones/validarProducto.js | 138 ++++++++++++ .../Validaciones/validarProveedor.js | 84 +++++++ .../Validaciones/validarVariante.js | 44 ++++ Utilidades/Servicios/enviarS3.js | 6 +- app.js | 2 + jsconfig.json | 7 + package.json | 7 + 42 files changed, 1546 insertions(+), 41 deletions(-) create mode 100644 Productos/Controladores/crearProducto.controller.js create mode 100644 Productos/Datos/Repositorios/repositorioCrearOpcion.js create mode 100644 Productos/Datos/Repositorios/repositorioCrearProducto.js create mode 100644 Productos/Datos/Repositorios/repositorioCrearVariante.js create mode 100644 Productos/Datos/Repositorios/repositorioProductoImagen.js create mode 100644 Productos/Datos/Repositorios/repositorioVarianteImagen.js create mode 100644 Productos/Rutas/RutasIndividuales/crearProducto.routes.js create mode 100644 Proveedores/Controladores/consultarProveedores.controller.js create mode 100644 Proveedores/Controladores/crearProveedor.controller.js create mode 100644 Proveedores/Datos/Repositorios/repositorioConsultarProveedores.js create mode 100644 Proveedores/Datos/Repositorios/repositorioCrearProvedor.js create mode 100644 Proveedores/Rutas/RutasIndividuales/consultarProveedores.routes.js create mode 100644 Proveedores/Rutas/RutasIndividuales/crearProveedor.routes.js create mode 100644 Proveedores/Rutas/indexProveedores.routes.js create mode 100644 Utilidades/Constantes/consultasImagenes.js create mode 100644 Utilidades/Constantes/consultasOpciones.js create mode 100644 Utilidades/Constantes/consultasProveedores.js create mode 100644 Utilidades/Constantes/consultasVariantes.js create mode 100644 Utilidades/Constantes/mensajesProveedores.js create mode 100644 Utilidades/Intermediarios/Validaciones/validarOpciones.js create mode 100644 Utilidades/Intermediarios/Validaciones/validarProducto.js create mode 100644 Utilidades/Intermediarios/Validaciones/validarProveedor.js create mode 100644 Utilidades/Intermediarios/Validaciones/validarVariante.js diff --git a/Clientes/Controladores/eliminarCliente.controller.js b/Clientes/Controladores/eliminarCliente.controller.js index 686f7d68..9131470e 100644 --- a/Clientes/Controladores/eliminarCliente.controller.js +++ b/Clientes/Controladores/eliminarCliente.controller.js @@ -1,8 +1,13 @@ +// Importaciones necesarias const repositorio = require('@altertex/cli/repos/repositorioEliminarCliente'); const MENSAJES_CLIENTES = require('@altertex/util/const/mensajesClientes'); +const extraerNombreArchivoS3 = require('@altertex/util/ser/extraerNombreArchivoS3'); +const eliminarImagenS3 = require('@altertex/util/ser/eliminarImagenS3'); +const correrQuery = require('@altertex/util/ser/correrQuery'); +const CONSULTAS_CLIENTES = require('@altertex/util/const/consultasClientes'); /** - * Controlador para eliminar un cliente de la base de datos. + * Controlador para eliminar un cliente de la base de datos y su imagen de S3. * @see [RF15 - Elimina Cliente](https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF15) * * @async @@ -28,6 +33,18 @@ exports.eliminarCliente = async (req, res) => { .json({ mensaje: MENSAJES_CLIENTES.CLIENTE_INVALIDO.mensaje }); } + // Obtener nombre de la imagen asociada (si existe) + let nombreImagen = ''; + try { + const resultadoImagen = await correrQuery(CONSULTAS_CLIENTES.OBTENER_NOMBRE_IMAGEN, [idCliente]); + if (resultadoImagen.length > 0 && resultadoImagen[0].urlImagen) { + nombreImagen = extraerNombreArchivoS3(resultadoImagen[0].urlImagen); + } + } catch { + // console.error('Error al obtener nombre de imagen:', error); + } + + // Eliminar cliente const resultado = await repositorio.eliminarClientePorId(idCliente); if (resultado.affectedRows === 0) { @@ -36,6 +53,11 @@ exports.eliminarCliente = async (req, res) => { .json({ mensaje: MENSAJES_CLIENTES.CLIENTE_NO_ENCONTRADO.mensaje }); } + // Eliminar imagen si hay nombre válido + if (nombreImagen) { + await eliminarImagenS3('clientes/', nombreImagen); + } + return res .status(MENSAJES_CLIENTES.CLIENTE_ELIMINADO.codigo) .json({ mensaje: MENSAJES_CLIENTES.CLIENTE_ELIMINADO.mensaje }); diff --git a/Empleados/Controladores/consultarLista.controller.js b/Empleados/Controladores/consultarLista.controller.js index df7d3a20..433e0e00 100644 --- a/Empleados/Controladores/consultarLista.controller.js +++ b/Empleados/Controladores/consultarLista.controller.js @@ -33,12 +33,6 @@ exports.consultarLista = async (req, res) => { try { const resultados = await repositorio.obtenerEmpleados(idCliente); - if (!resultados || resultados.length === 0) { - return res - .status(MENSAJES_EMPLEADOS.SIN_RESULTADOS.codigo) - .json({ mensaje: MENSAJES_EMPLEADOS.SIN_RESULTADOS.mensaje }); - } - return res.status(MENSAJES_EMPLEADOS.CONSULTA_EXITOSA.codigo).json({ mensaje: MENSAJES_EMPLEADOS.CONSULTA_EXITOSA.mensaje, empleados: resultados, diff --git a/Empleados/Controladores/consultarListaGrupos.controller.js b/Empleados/Controladores/consultarListaGrupos.controller.js index e59897f8..c26fa618 100644 --- a/Empleados/Controladores/consultarListaGrupos.controller.js +++ b/Empleados/Controladores/consultarListaGrupos.controller.js @@ -33,12 +33,6 @@ exports.consultarLista = async (req, res) => { try { const resultados = await repositorio.obtenerGrupoDeEmpleados(idCliente); - if (!resultados || resultados.length === 0) { - return res - .status(MENSAJES_GRUPO_EMPLEADOS.SIN_RESULTADOS.codigo) - .json({ mensaje: MENSAJES_GRUPO_EMPLEADOS.SIN_RESULTADOS.mensaje }); - } - return res.status(MENSAJES_GRUPO_EMPLEADOS.CONSULTA_EXITOSA.codigo).json({ mensaje: MENSAJES_GRUPO_EMPLEADOS.CONSULTA_EXITOSA.mensaje, grupoEmpleados: resultados, diff --git a/Empleados/Controladores/importarEmpleados.controller.js b/Empleados/Controladores/importarEmpleados.controller.js index 104989d4..b2d4b3fc 100644 --- a/Empleados/Controladores/importarEmpleados.controller.js +++ b/Empleados/Controladores/importarEmpleados.controller.js @@ -41,6 +41,7 @@ const MENSAJES_USUARIOS = require('@altertex/util/const/mensajesUsuarios'); * */ exports.importarEmpleados = async (req, res) => { + const idCliente = parseInt(req.user.clienteSeleccionado); const empleados = req.body; if (!Array.isArray(empleados) || empleados.length === 0) { @@ -99,8 +100,8 @@ exports.importarEmpleados = async (req, res) => { continue; } - if (!datos.idCliente) { - errores.push({ fila, error: 'El cliente es requerido' }); + if (typeof datos.idCliente !== 'undefined' && datos.idCliente !== '' && datos.idCliente !== null) { + errores.push({ fila, error: 'El cliente no debe ser incluido en el archivo' }); continue; } @@ -175,8 +176,12 @@ exports.importarEmpleados = async (req, res) => { }); } + for (const empleado of listaParaImportar) { + empleado.idCliente = idCliente; + } + try { - await repositorio.importarEmpleadosMasivo(empleados); + await repositorio.importarEmpleadosMasivo(listaParaImportar); } catch (error) { errores.push({ fila: "N/A", diff --git a/Empleados/Datos/Repositorios/repositorioEmpleados.js b/Empleados/Datos/Repositorios/repositorioEmpleados.js index 7c0e1702..99dcbd3b 100644 --- a/Empleados/Datos/Repositorios/repositorioEmpleados.js +++ b/Empleados/Datos/Repositorios/repositorioEmpleados.js @@ -21,10 +21,6 @@ exports.obtenerEmpleados = async (idCliente) => { try { const empleados = await correrQuery(query, [idCliente]); - if (!empleados || empleados.length === 0) { - throw new Error('No hay empleados'); - } - return empleados; } catch (error) { console.error('Error al obtener los empleados:', error); diff --git a/Empleados/Datos/Repositorios/repositorioGrupoDeEmpleados.js b/Empleados/Datos/Repositorios/repositorioGrupoDeEmpleados.js index 31fba0d5..b7180ad0 100644 --- a/Empleados/Datos/Repositorios/repositorioGrupoDeEmpleados.js +++ b/Empleados/Datos/Repositorios/repositorioGrupoDeEmpleados.js @@ -21,10 +21,6 @@ exports.obtenerGrupoDeEmpleados = async (idCliente) => { try { const gruposDeEmpleados = await correrQuery(query, [idCliente]); - if (!gruposDeEmpleados || gruposDeEmpleados.length === 0) { - throw new Error('No hay grupos de empleados'); - } - return gruposDeEmpleados; } catch (error) { console.error('Error al obtener el grupo de empleados:', error); diff --git a/Empleados/Rutas/RutasIndividuales/importarEmpleados.routes.js b/Empleados/Rutas/RutasIndividuales/importarEmpleados.routes.js index 4e281537..351ca763 100644 --- a/Empleados/Rutas/RutasIndividuales/importarEmpleados.routes.js +++ b/Empleados/Rutas/RutasIndividuales/importarEmpleados.routes.js @@ -8,6 +8,7 @@ const autorizarToken = require('@altertex/util/inter/autorizarToken'); const verificarPermisos = require('@altertex/util/inter/verificarPermisos'); const PERMISOS = require('@altertex/util/const/permisos'); const RUTAS = require('@altertex/util/const/rutas'); +const validarYSanitizar = require("@altertex/util/inter/validarYSanitizar"); /** * @swagger @@ -159,6 +160,7 @@ ruteador.post( RUTAS.EMPLEADOS.IMPORTAR_EMPLEADOS, revisarApiKey(), autorizarToken, + validarYSanitizar, verificarPermisos(PERMISOS.IMPORTAR_EMPLEADOS), controlador.importarEmpleados ); diff --git a/Productos/Controladores/crearProducto.controller.js b/Productos/Controladores/crearProducto.controller.js new file mode 100644 index 00000000..c06a54de --- /dev/null +++ b/Productos/Controladores/crearProducto.controller.js @@ -0,0 +1,205 @@ +//RF26 Crea Producto - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF26 +const multer = require('multer'); +const enviarS3 = require('@altertex/util/ser/enviarS3'); +const MENSAJES_PRODUCTOS = require('@altertex/util/const/mensajesProductos'); +const validarProducto = require('@altertex/util/vali/validarProducto'); +const validarVariante = require('@altertex/util/vali/validarVariante'); +const validarOpciones = require('@altertex/util/vali/validarOpciones'); +const repositorioCrearProducto = require('@altertex/pro/repos/repositorioCrearProducto'); +const repositorioProductoImagen = require('@altertex/pro/repos/repositorioProductoImagen'); +const repositorioCrearVariante = require('@altertex/pro/repos/repositorioCrearVariante'); +const repositorioVarianteImagen = require('@altertex/pro/repos/repositorioVarianteImagen'); +const repositorioCrearOpcion = require('@altertex/pro/repos/repositorioCrearOpcion'); +const conexion = require('@altertex/util/bd/db').promise(); + +const upload = multer({ storage: multer.memoryStorage() }); + +/** + * Controlador para crear un producto. + * + * Este controlador maneja la creación de un producto, incluyendo la validación de datos y el manejo de archivos de imagen. + * Realiza la creación del proveedor, producto, variantes y las imágenes asociadas, almacenándolas en un servicio S3. + * Si ocurre algún error en cualquier parte del proceso, se realiza un rollback de la transacción. + * + * @param {object} req - El objeto de solicitud. + * @param {object} req.user - El usuario autenticado. + * @param {string} req.user.clienteSeleccionado - El ID del cliente seleccionado por el usuario. + * @param {string} req.body.proveedor - EL ID del proveedor seleccionado por el usuario. + * @param {object} req.body - El cuerpo de la solicitud. + * @param {string} req.body.producto - Información del producto en formato JSON. + * @param {string} req.body.variantes - Información de las variantes del producto en formato JSON. + * @param {string} req.body.mapaImagenes - Información del mapa de imagenes de las variantes en formato JSON. + * @param {object} req.files - Archivos enviados en la solicitud. + * @param {Array} req.files.imagenProducto - La imagen principal del producto. + * @param {Array} req.files.imagenesVariante - Las imágenes asociadas a las variantes del producto. + * + * @param {object} res - El objeto de respuesta. + * @param {Function} res.status - Método para establecer el código de estado HTTP en la respuesta. + * @param {Function} res.json - Método para enviar una respuesta JSON. + * + * @returns {object} Retorna un mensaje de éxito si el producto se crea correctamente, o un mensaje de error si falla alguna validación o proceso. + * + * @example + * // Ejemplo de cómo usar el controlador + * // Se hace una solicitud POST a /crear-producto con el cuerpo de la solicitud que contiene el proveedor, producto, variantes y archivos de imagen. + * + * // Respuesta exitosa: + * res.status(200).json({ mensaje: 'Producto creado correctamente' }); + * + * // Respuesta de error: + * res.status(400).json({ mensaje: 'Error al crear producto', error: 'Error específico' }); + */ +exports.crearProducto = [ + upload.fields([ + { name: 'imagenProducto', maxCount: 1 }, + { name: 'imagenesVariante', maxCount: 100 }, + ]), + + async (req, res) => { + const idCliente = parseInt(req.user.clienteSeleccionado); + const producto = JSON.parse(req.body.producto); + const variantes = JSON.parse(req.body.variantes); + const mapaImagenes = JSON.parse(req.body.mapaImagenes); + const imagenProducto = req.files.imagenProducto ? req.files.imagenProducto[0] : null; + const imagenesVariante = req.files.imagenesVariante || []; + + // prettier-ignore + if ( + !idCliente + || !mapaImagenes + || !producto + || !Array.isArray(variantes) + || variantes.length === 0 + || !imagenProducto + || !imagenesVariante + ) { + return res.status(MENSAJES_PRODUCTOS.PARAMETROS_INVALIDOS.codigo).json({ + mensaje: MENSAJES_PRODUCTOS.PARAMETROS_INVALIDOS.mensaje, + }); + } + + const errorProducto = validarProducto(producto); + if (errorProducto) { + return res.status(MENSAJES_PRODUCTOS.PARAMETROS_INVALIDOS.codigo).json({ + mensaje: errorProducto.error, + }); + } + + if (imagenesVariante.length !== mapaImagenes.length) { + return res.status(MENSAJES_PRODUCTOS.PARAMETROS_INVALIDOS.codigo).json({ + mensaje: 'La cantidad de imágenes no coincide con el mapa de imágenes', + }); + } + + try { + await conexion.beginTransaction(); + + const idProducto = await repositorioCrearProducto.crearProducto(idCliente, producto); + if (!idProducto) { + throw new Error('Error al crear producto'); + } + + const varianteIdMap = {}; + const variantesPromises = variantes.map(async (variante) => { + const errorVariante = validarVariante({ + nombreVariante: variante.nombreVariante, + descripcion: variante.descripcion, + }); + if (errorVariante) { + throw new Error(errorVariante.error); + } + + const idVariante = await repositorioCrearVariante.crearVariante(idProducto, variante); + if (!idVariante) { + throw new Error('Error al crear variante'); + } + + varianteIdMap[variante.identificador] = { + id: idVariante, + nombre: variante.nombreVariante, + }; + + const errorOpciones = validarOpciones(variante.opciones); + if (errorOpciones) { + throw new Error(errorOpciones.error); + } + + await repositorioCrearOpcion.crearOpcion(idVariante, variante.opciones); + }); + + await Promise.all(variantesPromises); + + const urlImagenProductoPromise = imagenProducto + ? enviarS3({ + Bucket: process.env.AWS_BUCKET_NAME, + Key: `productos/${imagenProducto.originalname}`, + Body: imagenProducto.buffer, + ContentType: imagenProducto.mimetype, + }) + : Promise.resolve(null); + + // prettier-ignore + const urlImagenVariantePromises = imagenesVariante.map((imagenVariante) => + enviarS3({ + Bucket: process.env.AWS_BUCKET_NAME, + Key: `productos/${imagenVariante.originalname}`, + Body: imagenVariante.buffer, + ContentType: imagenVariante.mimetype, + })); + + const [urlImagenProducto, ...urlImagenVariantes] = await Promise.all([ + urlImagenProductoPromise, + ...urlImagenVariantePromises, + ]); + + if ((imagenProducto && !urlImagenProducto) || urlImagenVariantes.includes(null)) { + throw new Error('Error al subir imágenes al servidor'); + } + + if (imagenProducto) { + await repositorioProductoImagen.crearImagen( + idProducto, + imagenProducto.originalname, + producto.nombreComun + ); + } + + const imagenesVariantePromises = imagenesVariante.map(async (imagen, index) => { + const { idVariante: tempIdVariante } = mapaImagenes[index]; + const varianteInfo = varianteIdMap[tempIdVariante]; + + if (!varianteInfo) { + throw new Error(`Variante con ID temporal ${tempIdVariante} no encontrada`); + } + + await repositorioVarianteImagen.crearImagen( + varianteInfo.id, + imagen.originalname, + varianteInfo.nombre + ); + }); + + await Promise.all(imagenesVariantePromises); + + await conexion.commit(); + return res.status(200).json({ mensaje: 'Producto creado correctamente' }); + } catch (error) { + await conexion.rollback(); + + let errorMensaje = MENSAJES_PRODUCTOS.ERROR_CREAR_PRODUCTO; + + if (error.message.includes('Error al subir imágenes al servidor')) { + errorMensaje = MENSAJES_PRODUCTOS.ERROR_ENVIAR_IMAGENES_S3; + } else if (error.message.includes('Error al crear variante')) { + errorMensaje = MENSAJES_PRODUCTOS.ERROR_CREAR_VARIANTE; + } else if (error.message.includes('Error al asociar imagen con variante')) { + errorMensaje = MENSAJES_PRODUCTOS.ERROR_CREAR_IMAGEN_VARIANTE; + } + + return res.status(errorMensaje.codigo).json({ + mensaje: errorMensaje.mensaje, + error: error.message, + }); + } + }, +]; diff --git a/Productos/Datos/Repositorios/repositorioCrearOpcion.js b/Productos/Datos/Repositorios/repositorioCrearOpcion.js new file mode 100644 index 00000000..0d302a9b --- /dev/null +++ b/Productos/Datos/Repositorios/repositorioCrearOpcion.js @@ -0,0 +1,33 @@ +//RF26 Crea Producto - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF26 +const correrQuery = require('@altertex/util/ser/correrQuery'); +const consultas = require('@altertex/util/const/consultasOpciones'); + +/** + * Crea una o varias opciones relacionadas con una variante. + * + * @async + * @function crearOpcion + * @param {number} idVariante - ID de la variante a la que se le agregarán las opciones. + * @param {Array} opciones - Un arreglo con los objetos de opciones que se desean agregar. + * @returns {Promise} - Una promesa que se resuelve cuando todas las opciones se crean exitosamente. + */ +exports.crearOpcion = async (idVariante, opciones) => { + const query = consultas.CREAR; + + const promises = opciones.map(async (opcion) => { + const params = [ + idVariante, + opcion.cantidad, + opcion.valorOpcion, + opcion.SKUautomatico, + opcion.SKUcomercial, + opcion.costoAdicional, + opcion.descuento, + opcion.estado, + ]; + + await correrQuery(query, params); + }); + + await Promise.all(promises); +}; diff --git a/Productos/Datos/Repositorios/repositorioCrearProducto.js b/Productos/Datos/Repositorios/repositorioCrearProducto.js new file mode 100644 index 00000000..21b9df48 --- /dev/null +++ b/Productos/Datos/Repositorios/repositorioCrearProducto.js @@ -0,0 +1,62 @@ +//RF26 Crea Producto - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF26 +const correrQuery = require('@altertex/util/ser/correrQuery'); +const consultas = require('@altertex/util/const/consultasProductos'); + +/** + * Crea un nuevo producto en la base de datos. + * + * Esta función ejecuta una consulta SQL para insertar un producto en la base de datos, + * utilizando los parámetros proporcionados. Devuelve el ID del producto recién creado + * en caso de éxito, o un array vacío si ocurre algún error durante la operación. + * + * @param {number} clienteSeleccionado - ID del cliente para el cual se está creando el producto. + * @param {object} producto - Objeto que contiene la información del producto. + * @param {number} producto.idProveedor - ID del proveedor asociado al producto. + * @param {string} producto.nombreComun - Nombre común del producto. + * @param {string} producto.nombreComercial - Nombre comercial del producto. + * @param {string} producto.descripcion - Descripción del producto. + * @param {string} producto.marca - Marca del producto. + * @param {string} producto.modelo - Modelo del producto. + * @param {string} producto.tipoProducto - Tipo del producto. + * @param {number} producto.precioPuntos - Precio en puntos del producto. + * @param {number} producto.precioCliente - Precio para el cliente del producto. + * @param {number} producto.precioVenta - Precio de venta del producto. + * @param {number} producto.costo - Costo de producción del producto. + * @param {number} producto.impuesto - Impuesto aplicado al producto. + * @param {number} producto.descuento - Descuento aplicado al producto. + * @param {string} producto.estado - Estado del producto (activo/inactivo). + * @param {boolean} producto.envio - Indica si el producto es apto para envío. + * + * @returns {number|Array} El ID del producto recién creado en caso de éxito, o un array vacío en caso de error. + */ +exports.crearProducto = async (clienteSeleccionado, producto) => { + const query = consultas.CREAR; + const parametros = [ + clienteSeleccionado, + producto.idProveedor, + producto.nombreComun, + producto.nombreComercial, + producto.descripcion, + producto.marca, + producto.modelo, + producto.tipoProducto, + producto.precioPuntos, + producto.precioCliente, + producto.precioVenta, + producto.costo, + producto.impuesto, + producto.descuento, + producto.estado, + producto.envio, + ]; + + try { + const resultados = await correrQuery(query, parametros); + + const idProducto = resultados.insertId; + return idProducto; + } catch (error) { + console.error('Error al crear producto:', error); + return []; + } +}; diff --git a/Productos/Datos/Repositorios/repositorioCrearVariante.js b/Productos/Datos/Repositorios/repositorioCrearVariante.js new file mode 100644 index 00000000..3170a92b --- /dev/null +++ b/Productos/Datos/Repositorios/repositorioCrearVariante.js @@ -0,0 +1,32 @@ +//RF26 Crea Producto - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF26 +const correrQuery = require('@altertex/util/ser/correrQuery'); +const consultas = require('@altertex/util/const/consultasVariantes'); + +/** + * Crea una nueva variante para un producto en la base de datos. + * + * Esta función ejecuta una consulta SQL para insertar una variante asociada a un producto, + * utilizando los parámetros proporcionados. Devuelve el ID de la variante recién creada + * en caso de éxito, o un array vacío si ocurre algún error durante la operación. + * + * @param {number} idProducto - ID del producto al que se le va a agregar la variante. + * @param {object} variante - Objeto que contiene la información de la variante. + * @param {string} variante.nombreVariante - Nombre de la variante (por ejemplo, color, tamaño). + * @param {string} variante.descripcion - Descripción de la variante. + * + * @returns {number|Array} El ID de la variante recién creada en caso de éxito, o un array vacío en caso de error. + */ +exports.crearVariante = async (idProducto, variante) => { + const query = consultas.CREAR; + const parametros = [idProducto, variante.nombreVariante, variante.descripcion]; + + try { + const resultados = await correrQuery(query, parametros); + + const idVariante = resultados.insertId; + return idVariante; + } catch (error) { + console.error('Error al crear variante:', error); + return []; + } +}; diff --git a/Productos/Datos/Repositorios/repositorioProductoImagen.js b/Productos/Datos/Repositorios/repositorioProductoImagen.js new file mode 100644 index 00000000..bc1cb8d0 --- /dev/null +++ b/Productos/Datos/Repositorios/repositorioProductoImagen.js @@ -0,0 +1,43 @@ +//RF26 Crea Producto - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF26 +const correrQuery = require('@altertex/util/ser/correrQuery'); +const consultasProductos = require('@altertex/util/const/consultasProductos'); +const consultasImagenes = require('@altertex/util/const/consultasImagenes'); + +/** + * Crea una imagen en la base de datos y la asocia a un producto. + * + * Esta función realiza dos operaciones en una transacción: + * - Inserta una nueva imagen en la tabla de imágenes. + * - Crea una relación entre la imagen y el producto correspondiente. + * + * @param {number} idProducto - ID del producto al que se asociará la imagen. + * @param {string} urlImagenProducto - URL o ruta de la imagen del producto almacenada. + * @param {string} nombreComun - Nombre común o descriptivo asociado al producto. + * + * @returns {Promise} El resultado de la inserción de la imagen (incluyendo `insertId`) si es exitoso, o un arreglo vacío en caso de error. + */ +exports.crearImagen = async (idProducto, urlImagenProducto, nombreComun) => { + const queryImagen = consultasImagenes.CREAR; + const queryRelacionImagenProducto = consultasProductos.CREAR_IMAGEN_PRODUCTO; + const parametrosImagen = [urlImagenProducto, 'Imagen Producto', nombreComun]; + + const conexion = require('@altertex/util/bd/db').promise(); + + try { + await conexion.beginTransaction(); + + const resultadoImagen = await correrQuery(queryImagen, parametrosImagen); + const idImagen = resultadoImagen.insertId; + + const parametrosRelacion = [idImagen, idProducto]; + await correrQuery(queryRelacionImagenProducto, parametrosRelacion); + + await conexion.commit(); + + return resultadoImagen; + } catch (error) { + console.error('Error al crear o asociar la imagen:', error); + await conexion.rollback(); + return []; + } +}; diff --git a/Productos/Datos/Repositorios/repositorioVarianteImagen.js b/Productos/Datos/Repositorios/repositorioVarianteImagen.js new file mode 100644 index 00000000..a15a94ef --- /dev/null +++ b/Productos/Datos/Repositorios/repositorioVarianteImagen.js @@ -0,0 +1,42 @@ +//RF26 Crea Producto - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF26 +const conexion = require('@altertex/util/bd/db').promise(); +const correrQuery = require('@altertex/util/ser/correrQuery'); +const consultasVariantes = require('@altertex/util/const/consultasVariantes'); +const consultasImagenes = require('@altertex/util/const/consultasImagenes'); + +/** + * Crea una imagen en la base de datos y la asocia a una variante de producto. + * + * Esta función realiza dos operaciones dentro de una transacción: + * - Inserta una nueva imagen en la tabla de imágenes. + * - Crea una relación entre la imagen y la variante correspondiente. + * + * @param {number} idVariante - ID de la variante a la que se asociará la imagen. + * @param {string} urlImagenVariante - URL o ruta de la imagen de la variante almacenada. + * @param {string} nombreComun - Nombre común o descriptivo asociado a la variante. + * + * @returns {Promise} El resultado de la inserción de la imagen (incluyendo `insertId`) si es exitoso, o un arreglo vacío en caso de error. + */ +exports.crearImagen = async (idVariante, urlImagenVariante, nombreComun) => { + const queryImagen = consultasImagenes.CREAR; + const queryRelacionImagenVariante = consultasVariantes.CREAR_IMAGEN_VARIANTE; + const parametrosImagen = [urlImagenVariante, 'Imagen Variante', nombreComun]; + + try { + await conexion.beginTransaction(); + + const resultadoImagen = await correrQuery(queryImagen, parametrosImagen); + const idImagen = resultadoImagen.insertId; + + const parametrosRelacion = [idImagen, idVariante]; + await correrQuery(queryRelacionImagenVariante, parametrosRelacion); + + await conexion.commit(); + + return resultadoImagen; + } catch (error) { + console.error('Error al crear o asociar la imagen:', error); + await conexion.rollback(); + return []; + } +}; diff --git a/Productos/Rutas/RutasIndividuales/consultarProductos.routes.js b/Productos/Rutas/RutasIndividuales/consultarProductos.routes.js index 6e706544..985e230a 100644 --- a/Productos/Rutas/RutasIndividuales/consultarProductos.routes.js +++ b/Productos/Rutas/RutasIndividuales/consultarProductos.routes.js @@ -1,4 +1,4 @@ -//RF[27] Consulta Lista de Productos - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF27] +//RF27 Consulta Lista de Productos - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF27 const express = require('express'); const ruteador = express.Router(); const controlador = require('@altertex/pro/ctrl/consultarProductos.controller'); diff --git a/Productos/Rutas/RutasIndividuales/crearProducto.routes.js b/Productos/Rutas/RutasIndividuales/crearProducto.routes.js new file mode 100644 index 00000000..f50e7394 --- /dev/null +++ b/Productos/Rutas/RutasIndividuales/crearProducto.routes.js @@ -0,0 +1,116 @@ +//RF26 Crea Producto - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF26 +const express = require('express'); +const ruteador = express.Router(); +const controlador = require('@altertex/pro/ctrl/crearProducto.controller'); +const revisarApiKey = require('@altertex/util/inter/revisarApiKey'); +const autorizarToken = require('@altertex/util/inter/autorizarToken'); +const verificarPermisos = require('@altertex/util/inter/verificarPermisos'); + +const PERMISOS = require('@altertex/util/const/permisos'); +const RUTAS = require('@altertex/util/const/rutas'); + +/** + * @swagger + * /api/productos/crear: + * post: + * summary: Crear un nuevo producto + * description: Crea un producto con sus variantes e imágenes asociadas + * tags: [Productos] + * security: + * - ApiKeyAuth: [] + * - BearerAuth: [] + * consumes: + * - multipart/form-data + * requestBody: + * required: true + * content: + * multipart/form-data: + * schema: + * type: object + * required: + * - producto + * - variantes + * - mapaImagenes + * - imagenProducto + * - imagenesVariante + * properties: + * producto: + * type: string + * format: json + * description: Información del producto en formato JSON + * example: '{"nombreComun":"Camisa casual","nombreComercial":"Camisa formal","descripcion":"Camisa de algodón","marca":"Brand","modelo":"M-123","tipoProducto":"Ropa","precioPuntos":100,"precioCliente":50.99,"precioVenta":59.99,"costo":30.00,"impuesto":16,"descuento":0,"idProveedor":1,"estado":1,"envio":1}' + * variantes: + * type: string + * format: json + * description: Array de variantes del producto en formato JSON + * example: '[{"identificador":"var1","nombreVariante":"Color","descripcion":"Color de la prenda","opciones":[{"valorOpcion":"Azul","cantidad":10,"descuento":0,"costoAdicional":0,"SKUautomatico":"SKU-AUTO-1","SKUcomercial":"SKU-COM-1"}]}]' + * mapaImagenes: + * type: string + * format: json + * description: Mapa que relaciona imágenes con variantes en formato JSON + * example: '[{"idVariante":"var1"}]' + * imagenProducto: + * type: string + * format: binary + * description: Imagen principal del producto + * imagenesVariante: + * type: array + * items: + * type: string + * format: binary + * description: Imágenes asociadas a cada variante del producto + * responses: + * 200: + * description: Producto creado correctamente + * content: + * application/json: + * schema: + * type: object + * properties: + * mensaje: + * type: string + * example: Producto creado correctamente + * 400: + * description: Error en los parámetros proporcionados + * content: + * application/json: + * schema: + * type: object + * properties: + * mensaje: + * type: string + * example: Los parámetros proporcionados no son válidos + * 401: + * description: No autorizado, token inválido o falta de permisos + * content: + * application/json: + * schema: + * type: object + * properties: + * mensaje: + * type: string + * example: No tiene permisos para realizar esta acción + * 500: + * description: Error interno del servidor + * content: + * application/json: + * schema: + * type: object + * properties: + * mensaje: + * type: string + * example: Error al crear producto + * error: + * type: string + * example: Error al crear variante + */ + +ruteador.post( + RUTAS.PRODUCTOS.CREAR, + revisarApiKey(), + autorizarToken, + verificarPermisos(PERMISOS.CREAR_PRODUCTO), + controlador.crearProducto +); + +module.exports = ruteador; diff --git a/Productos/Rutas/RutasIndividuales/eliminarProducto.routes.js b/Productos/Rutas/RutasIndividuales/eliminarProducto.routes.js index db33f7b6..0a25d53a 100644 --- a/Productos/Rutas/RutasIndividuales/eliminarProducto.routes.js +++ b/Productos/Rutas/RutasIndividuales/eliminarProducto.routes.js @@ -6,7 +6,7 @@ const revisarApiKey = require('@altertex/util/inter/revisarApiKey'); const autorizarToken = require('@altertex/util/inter/autorizarToken'); const verificarPermisos = require('@altertex/util/inter/verificarPermisos'); -const PERMISOS = require('../../../Utilidades/Constantes/permisos'); +const PERMISOS = require('@altertex/util/const/permisos'); const RUTAS = require('@altertex/util/const/rutas'); /** diff --git a/Productos/Rutas/indexProductos.routes.js b/Productos/Rutas/indexProductos.routes.js index c2aceb14..47a7202e 100644 --- a/Productos/Rutas/indexProductos.routes.js +++ b/Productos/Rutas/indexProductos.routes.js @@ -1,13 +1,16 @@ const express = require('express'); const ruteador = express.Router(); - -const rutaConsultar = require('@altertex/pro/rutasInd/consultarProductos.routes'); +const rutaConsultarLista = require('@altertex/pro/rutasInd/consultarProductos.routes'); const rutaEliminar = require("@altertex/pro/rutasInd/eliminarProducto.routes"); +const rutaCrearProducto = require('@altertex/pro/rutasInd/crearProducto.routes'); const RUTAS = require('@altertex/util/const/rutas'); -// RF[27] Consulta Lista de Productos - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF27] +//RF27 Consulta Lista de Productos - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF27 +ruteador.use(RUTAS.PRODUCTOS.BASE, rutaConsultarLista); +//RF26 Crea Producto - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF26 +ruteador.use(RUTAS.PRODUCTOS.BASE, rutaCrearProducto); // RF[30] Eliminar Producto - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF30] -ruteador.use(RUTAS.PRODUCTOS.BASE, rutaConsultar, rutaEliminar); +ruteador.use(RUTAS.PRODUCTOS.BASE, rutaEliminar); module.exports = ruteador; \ No newline at end of file diff --git a/Proveedores/Controladores/consultarProveedores.controller.js b/Proveedores/Controladores/consultarProveedores.controller.js new file mode 100644 index 00000000..418c9b02 --- /dev/null +++ b/Proveedores/Controladores/consultarProveedores.controller.js @@ -0,0 +1,51 @@ +//RF26 Crea Producto - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF26 +const repositorio = require('@altertex/prove/repos/repositorioConsultarProveedores'); +const MENSAJES_PROVEEDORES = require('@altertex/util/const/mensajesProveedores'); + +/** + * Controlador para la consulta de la lista de proveedores de un cliente. + * + * @async + * @function consultarLista + * @param {object} req - Objeto de solicitud de Express. + * @param {object} req.user - Datos del usuario autenticado. + * @param {number} req.user.clienteSeleccionado - ID del cliente seleccionado para la consulta. + * @param {object} res - Objeto de respuesta de Express. + * + * @returns {Response} Respuesta HTTP con estado: + * - 200 si la consulta es exitosa, junto con los datos de los proveedores. + * - 204 si no hay proveedores registrados. + * - 400 si los parámetros proporcionados son inválidos. + * - 500 si ocurre un error en el servidor. + * + * @throws {Error} Si ocurre un error inesperado durante la operación. + */ +exports.consultarLista = async (req, res) => { + const idCliente = parseInt(req.user?.clienteSeleccionado); + + if (!idCliente || isNaN(idCliente)) { + return res.status(MENSAJES_PROVEEDORES.DATOS_PROVEEDOR_INVALIDOS.codigo).json({ + mensaje: MENSAJES_PROVEEDORES.DATOS_PROVEEDOR_INVALIDOS.mensaje, + }); + } + + try { + const resultados = await repositorio.obtenerProveedores(idCliente); + + if (!resultados || resultados.length === 0) { + return res.status(MENSAJES_PROVEEDORES.LISTA_PROVEEDORES_VACIA.codigo).json({ + mensaje: MENSAJES_PROVEEDORES.LISTA_PROVEEDORES_VACIA.mensaje, + }); + } + + return res.status(MENSAJES_PROVEEDORES.CONSULTA_PROVEEDORES_EXITOSA.codigo).json({ + mensaje: MENSAJES_PROVEEDORES.CONSULTA_PROVEEDORES_EXITOSA.mensaje, + listaProveedores: resultados, + }); + } catch (error) { + console.error('Error al consultar proveedores:', error); + return res.status(MENSAJES_PROVEEDORES.ERROR_CONSULTAR_PROVEEDORES.codigo).json({ + mensaje: MENSAJES_PROVEEDORES.ERROR_CONSULTAR_PROVEEDORES.mensaje, + }); + } +}; diff --git a/Proveedores/Controladores/crearProveedor.controller.js b/Proveedores/Controladores/crearProveedor.controller.js new file mode 100644 index 00000000..5bcaac0f --- /dev/null +++ b/Proveedores/Controladores/crearProveedor.controller.js @@ -0,0 +1,50 @@ +//RF26 Crea Producto - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF26 +const validarProveedor = require('@altertex/util/vali/validarProveedor'); +const repositorioCrearProveedor = require('@altertex/prove/repos/repositorioCrearProvedor'); +const MENSAJES_PROVEEDORES = require('@altertex/util/const/mensajesProveedores'); + +/** + * Controlador para crear un proveedor de forma independiente. + * + * @async + * @function crearProveedor + * @param {object} req - Objeto de solicitud de Express. + * @param {object} req.body - Cuerpo de la solicitud HTTP. + * @param {object} res - Objeto de respuesta de Express. + * + * @returns {Response} Respuesta HTTP con estado: + * - 200 si la creación es exitosa, junto con el ID del proveedor. + * - 400 si los parámetros proporcionados son inválidos. + * - 500 si ocurre un error en el servidor. + */ +exports.crearProveedor = async (req, res) => { + const proveedor = req.body; + const idCliente = parseInt(req.user.clienteSeleccionado); + + console.log(req.body); + + const errorProveedor = validarProveedor(proveedor); + if (errorProveedor) { + return res.status(MENSAJES_PROVEEDORES.DATOS_PROVEEDOR_INVALIDOS.codigo).json({ + mensaje: MENSAJES_PROVEEDORES.DATOS_PROVEEDOR_INVALIDOS.mensaje, + }); + } + + try { + const idProveedor = await repositorioCrearProveedor.crearProveedor(idCliente, proveedor); + + if (!idProveedor) { + throw new Error('Error al crear proveedor'); + } + + return res.status(MENSAJES_PROVEEDORES.PROVEEDOR_CREADO_EXITOSAMENTE.codigo).json({ + mensaje: MENSAJES_PROVEEDORES.PROVEEDOR_CREADO_EXITOSAMENTE.mensaje, + }); + } catch (error) { + console.error('Error al crear proveedor:', error); + return res.status(MENSAJES_PROVEEDORES.ERROR_CREAR_PROVEEDOR.codigo).json({ + mensaje: MENSAJES_PROVEEDORES.ERROR_CREAR_PROVEEDOR.mensaje, + error: error.message, + }); + } +}; diff --git a/Proveedores/Datos/Repositorios/repositorioConsultarProveedores.js b/Proveedores/Datos/Repositorios/repositorioConsultarProveedores.js new file mode 100644 index 00000000..9f1ea2df --- /dev/null +++ b/Proveedores/Datos/Repositorios/repositorioConsultarProveedores.js @@ -0,0 +1,25 @@ +//RF26 Crea Producto - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF26 +const correrQuery = require('@altertex/util/ser/correrQuery'); +const consultas = require('@altertex/util/const/consultasProveedores'); + +/** + * Obtiene la lista de proveedores registrados para un cliente específico. + * + * Esta función ejecuta una consulta SQL para recuperar los proveedores asociados + * al cliente proporcionado. Devuelve un arreglo de proveedores si la operación + * es exitosa, o un arreglo vacío en caso de error. + * + * @param {string|number} clienteSeleccionado - ID o identificador del cliente del cual se quieren obtener los proveedores. + * + * @returns {Promise} Un arreglo de objetos que representan los proveedores, o un arreglo vacío si ocurre un error. + */ +exports.obtenerProveedores = async (clienteSeleccionado) => { + const query = consultas.OBTENER_LISTA; + try { + const resultados = await correrQuery(query, [clienteSeleccionado]); + return resultados; + } catch (error) { + console.error('Error al obtener los proveedores:', error); + return []; + } +}; diff --git a/Proveedores/Datos/Repositorios/repositorioCrearProvedor.js b/Proveedores/Datos/Repositorios/repositorioCrearProvedor.js new file mode 100644 index 00000000..cc674bb4 --- /dev/null +++ b/Proveedores/Datos/Repositorios/repositorioCrearProvedor.js @@ -0,0 +1,46 @@ +//RF26 Crea Producto - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF26 +const correrQuery = require('@altertex/util/ser/correrQuery'); +const consultas = require('@altertex/util/const/consultasProveedores'); + +/** + * Crea un nuevo proveedor en la base de datos. + * + * Esta función ejecuta una consulta SQL para insertar los datos de un proveedor + * utilizando los parámetros proporcionados. Devuelve el ID del proveedor recién creado + * en caso de éxito, o un array vacío si ocurre algún error durante la operación. + * + * @param {string|number} clienteSeleccionado - ID o identificador del cliente del cual se quieren obtener los proveedores. + * @param {object} proveedor - Objeto que contiene la información del proveedor. + * @param {string} proveedor.nombre - Nombre del contacto del proveedor. + * @param {string} proveedor.nombreCompania - Nombre de la compañía del proveedor. + * @param {string} proveedor.telefonoContacto - Teléfono de contacto del proveedor. + * @param {string} proveedor.direccion - Dirección del proveedor. + * @param {string} proveedor.codigoPostal - Código postal de la dirección del proveedor. + * @param {string} proveedor.pais - País del proveedor. + * @param {string} proveedor.estado - Estado o provincia del proveedor. + * + * @returns {Promise} El ID del proveedor recién creado en caso de éxito, o un arreglo vacío en caso de error. + */ +exports.crearProveedor = async (clienteSeleccionado, proveedor) => { + const query = consultas.CREAR; + const parametros = [ + clienteSeleccionado, + proveedor.nombre, + proveedor.nombreCompania, + proveedor.telefonoContacto, + proveedor.direccion, + proveedor.codigoPostal, + proveedor.pais, + proveedor.estado, + ]; + + try { + const resultados = await correrQuery(query, parametros); + + const idProveedor = resultados.insertId; + return idProveedor; + } catch (error) { + console.error('Error al crear proveedor:', error); + return []; + } +}; diff --git a/Proveedores/Rutas/RutasIndividuales/consultarProveedores.routes.js b/Proveedores/Rutas/RutasIndividuales/consultarProveedores.routes.js new file mode 100644 index 00000000..81d249d6 --- /dev/null +++ b/Proveedores/Rutas/RutasIndividuales/consultarProveedores.routes.js @@ -0,0 +1,126 @@ +//RF26 Crea Producto - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF26 +const express = require('express'); +const ruteador = express.Router(); +const controlador = require('@altertex/prove/ctrl/consultarProveedores.controller'); +const revisarApiKey = require('@altertex/util/inter/revisarApiKey'); +const autorizarToken = require('@altertex/util/inter/autorizarToken'); +const verificarPermisos = require('@altertex/util/inter/verificarPermisos'); + +const PERMISOS = require('@altertex/util/const/permisos'); +const RUTAS = require('@altertex/util/const/rutas'); + +/** + * @swagger + * /api/proveedores/consultar-lista: + * post: + * summary: Consultar lista de proveedores + * description: Obtiene la lista de proveedores asociados al cliente seleccionado + * tags: [Proveedores] + * security: + * - ApiKeyAuth: [] + * - BearerAuth: [] + * requestBody: + * required: false + * content: + * application/json: + * schema: + * type: object + * example: {} + * responses: + * 200: + * description: Consulta exitosa + * content: + * application/json: + * schema: + * type: object + * properties: + * mensaje: + * type: string + * example: Consulta de proveedores exitosa + * listaProveedores: + * type: array + * items: + * type: object + * properties: + * id: + * type: integer + * example: 1 + * nombre: + * type: string + * example: Juan Pérez + * nombreCompania: + * type: string + * example: Textiles del Norte S.A. + * telefonoContacto: + * type: string + * example: +52 55 1234 5678 + * correoContacto: + * type: string + * example: juan.perez@textilesnorte.com + * direccion: + * type: string + * example: Av. Industrial 123, Col. Centro + * codigoPostal: + * type: string + * example: 12345 + * pais: + * type: string + * example: México + * estado: + * type: integer + * example: 1 + * fechaCreacion: + * type: string + * format: date-time + * example: 2023-10-15T14:30:00Z + * 204: + * description: No hay proveedores registrados + * content: + * application/json: + * schema: + * type: object + * properties: + * mensaje: + * type: string + * example: No hay proveedores registrados + * 400: + * description: Parámetros inválidos + * content: + * application/json: + * schema: + * type: object + * properties: + * mensaje: + * type: string + * example: Los datos del proveedor son inválidos + * 401: + * description: No autorizado, token inválido o falta de permisos + * content: + * application/json: + * schema: + * type: object + * properties: + * mensaje: + * type: string + * example: No tiene permisos para realizar esta acción + * 500: + * description: Error interno del servidor + * content: + * application/json: + * schema: + * type: object + * properties: + * mensaje: + * type: string + * example: Error al consultar proveedores + */ + +ruteador.post( + RUTAS.PROVEEDORES.CONSULTAR_LISTA, + revisarApiKey(), + autorizarToken, + verificarPermisos(PERMISOS.CONSULTAR_PRODUCTOS), + controlador.consultarLista +); + +module.exports = ruteador; diff --git a/Proveedores/Rutas/RutasIndividuales/crearProveedor.routes.js b/Proveedores/Rutas/RutasIndividuales/crearProveedor.routes.js new file mode 100644 index 00000000..6eb7fad3 --- /dev/null +++ b/Proveedores/Rutas/RutasIndividuales/crearProveedor.routes.js @@ -0,0 +1,125 @@ +//RF26 Crea Producto - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF26 +const express = require('express'); +const ruteador = express.Router(); +const controlador = require('@altertex/prove/ctrl/crearProveedor.controller'); +const revisarApiKey = require('@altertex/util/inter/revisarApiKey'); +const autorizarToken = require('@altertex/util/inter/autorizarToken'); +const verificarPermisos = require('@altertex/util/inter/verificarPermisos'); + +const PERMISOS = require('@altertex/util/const/permisos'); +const RUTAS = require('@altertex/util/const/rutas'); + +/** + * @swagger + * /api/proveedores/crear: + * post: + * summary: Crear un nuevo proveedor + * description: Crea un nuevo proveedor asociado al cliente seleccionado + * tags: [Proveedores] + * security: + * - ApiKeyAuth: [] + * - BearerAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - nombre + * - nombreCompania + * - telefonoContacto + * - correoContacto + * - direccion + * - codigoPostal + * - pais + * - estado + * properties: + * nombre: + * type: string + * description: Nombre del contacto del proveedor + * example: Juan Pérez + * nombreCompania: + * type: string + * description: Nombre de la compañía del proveedor + * example: Textiles del Norte S.A. + * telefonoContacto: + * type: string + * description: Número telefónico de contacto + * example: +52 55 1234 5678 + * correoContacto: + * type: string + * description: Correo electrónico de contacto + * example: juan.perez@textilesnorte.com + * direccion: + * type: string + * description: Dirección física del proveedor + * example: Av. Industrial 123, Col. Centro + * codigoPostal: + * type: string + * description: Código postal de la dirección + * example: 12345 + * pais: + * type: string + * description: País donde se encuentra el proveedor + * example: México + * estado: + * type: number + * description: Estado del proveedor (1 activo, 0 inactivo) + * enum: [0, 1] + * example: 1 + * responses: + * 200: + * description: Proveedor creado exitosamente + * content: + * application/json: + * schema: + * type: object + * properties: + * mensaje: + * type: string + * example: Proveedor creado exitosamente + * 400: + * description: Datos del proveedor inválidos + * content: + * application/json: + * schema: + * type: object + * properties: + * mensaje: + * type: string + * example: Los datos del proveedor son inválidos + * 401: + * description: No autorizado, token inválido o falta de permisos + * content: + * application/json: + * schema: + * type: object + * properties: + * mensaje: + * type: string + * example: No tiene permisos para realizar esta acción + * 500: + * description: Error interno del servidor + * content: + * application/json: + * schema: + * type: object + * properties: + * mensaje: + * type: string + * example: Error al crear proveedor + * error: + * type: string + * example: Error al crear proveedor + */ + +ruteador.post( + RUTAS.PROVEEDORES.CREAR, + revisarApiKey(), + autorizarToken, + verificarPermisos(PERMISOS.CREAR_PRODUCTO), + controlador.crearProveedor +); + +module.exports = ruteador; diff --git a/Proveedores/Rutas/indexProveedores.routes.js b/Proveedores/Rutas/indexProveedores.routes.js new file mode 100644 index 00000000..f464092b --- /dev/null +++ b/Proveedores/Rutas/indexProveedores.routes.js @@ -0,0 +1,13 @@ +const express = require('express'); +const ruteador = express.Router(); +const rutaConsultarLista = require('@altertex/prove/rutasInd/consultarProveedores.routes'); +const rutaCrearProveedor = require('@altertex/prove/rutasInd/crearProveedor.routes'); + +const RUTAS = require('@altertex/util/const/rutas'); + +//RF26 Crea Producto - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF26 +ruteador.use(RUTAS.PROVEEDORES.BASE, rutaConsultarLista); +//RF26 Crea Producto - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF26 +ruteador.use(RUTAS.PROVEEDORES.BASE, rutaCrearProveedor); + +module.exports = ruteador; diff --git a/Roles/Datos/Repositorios/repositorioCrearRol.js b/Roles/Datos/Repositorios/repositorioCrearRol.js index 7f4242fe..5cf61ff2 100644 --- a/Roles/Datos/Repositorios/repositorioCrearRol.js +++ b/Roles/Datos/Repositorios/repositorioCrearRol.js @@ -1,5 +1,5 @@ -const db = require("@altertex/util/bd/db"); -const QUERY = require("@altertex/util/const/consultasRoles"); +const db = require('@altertex/util/bd/db'); +const QUERY = require('@altertex/util/const/consultasRoles'); /** * Verifica si un rol con el nombre especificado ya existe en la base de datos. @@ -68,6 +68,6 @@ exports.asociarPermisosARol = async (idRol, permisos) => { await conexion.commit(); } catch { await conexion.rollback(); - throw new Error("Error asociando permisos al rol"); + throw new Error('Error asociando permisos al rol'); } }; diff --git a/Utilidades/Constantes/consultasImagenes.js b/Utilidades/Constantes/consultasImagenes.js new file mode 100644 index 00000000..0c559a6f --- /dev/null +++ b/Utilidades/Constantes/consultasImagenes.js @@ -0,0 +1,6 @@ +module.exports = { + CREAR: ` + INSERT INTO imagen(urlImagen, tipoImagen, textoAlternativo) + VALUES (?, ?, ?); + `, +}; diff --git a/Utilidades/Constantes/consultasOpciones.js b/Utilidades/Constantes/consultasOpciones.js new file mode 100644 index 00000000..825ebfe9 --- /dev/null +++ b/Utilidades/Constantes/consultasOpciones.js @@ -0,0 +1,6 @@ +module.exports = { + CREAR: ` + INSERT INTO opcion (idVariante, cantidad, valorOpcion, SKUautomatico, SKUcomercial, costoAdicional, descuento, estado) + VALUES (?, ?, ?, ?, ?, ?, ?, ?); + `, +}; diff --git a/Utilidades/Constantes/consultasProductos.js b/Utilidades/Constantes/consultasProductos.js index 100d3680..81b5df4c 100644 --- a/Utilidades/Constantes/consultasProductos.js +++ b/Utilidades/Constantes/consultasProductos.js @@ -7,7 +7,23 @@ module.exports = { WHERE i.tipoImagen = "Imagen Producto" AND p.idCliente = ?; `, + CREAR: ` + INSERT INTO producto ( + idCliente, idProveedor, nombreComun, nombreComercial, descripcion, + marca, modelo, tipoProducto, precioPuntos, precioCliente, + precioVenta, costo, impuesto, descuento, estado, envio + ) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); + `, + CREAR_IMAGEN_PRODUCTO: ` + INSERT INTO imagen_producto (idImagen, idProducto) + VALUES (?, ?); + `, + CREAR_DATOS_ENVIO: ` + INSERT INTO datos_envio (idProducto, peso, longitud, ancho, altura, volumen, tipoPaquete) + VALUES (?, ?, ?, ?, ?, ?,?); + `, ELIMINAR_PRODUCTOS: "DELETE FROM producto WHERE idProducto IN (?)", -}; \ No newline at end of file +}; diff --git a/Utilidades/Constantes/consultasProveedores.js b/Utilidades/Constantes/consultasProveedores.js new file mode 100644 index 00000000..73fad2c5 --- /dev/null +++ b/Utilidades/Constantes/consultasProveedores.js @@ -0,0 +1,10 @@ +module.exports = { + OBTENER_LISTA: ` + SELECT * FROM proveedor + WHERE idCliente = ?; + `, + CREAR: ` + INSERT INTO proveedor (idCliente, nombre, nombreCompania, telefonoContacto, direccion, codigoPostal, pais, estado) + VALUES (?, ?, ?, ?, ?, ?, ?, ?); + `, +}; diff --git a/Utilidades/Constantes/consultasUsuarios.js b/Utilidades/Constantes/consultasUsuarios.js index 7c3b903e..f354157a 100644 --- a/Utilidades/Constantes/consultasUsuarios.js +++ b/Utilidades/Constantes/consultasUsuarios.js @@ -80,7 +80,13 @@ module.exports = { LEFT JOIN usuario_rol ur ON u.idUsuario = ur.idUsuario LEFT JOIN rol r ON ur.idRol = r.idRol LEFT JOIN usuario_cliente uc ON u.idUsuario = uc.idUsuario - LEFT JOIN cliente c ON uc.idCliente = c.idCliente; + LEFT JOIN cliente c ON uc.idCliente = c.idCliente + WHERE u.idUsuario NOT IN ( + SELECT ur2.idUsuario + FROM usuario_rol ur2 + WHERE ur2.idRol = 3 + ); + `, ELIMINAR_USUARIOS: ` diff --git a/Utilidades/Constantes/consultasVariantes.js b/Utilidades/Constantes/consultasVariantes.js new file mode 100644 index 00000000..b9ebc210 --- /dev/null +++ b/Utilidades/Constantes/consultasVariantes.js @@ -0,0 +1,10 @@ +module.exports = { + CREAR: ` + INSERT INTO variante (idProducto, nombreVariante, descripcion) + VALUES (?, ?, ?); + `, + CREAR_IMAGEN_VARIANTE: ` + INSERT INTO imagen_variante (idImagen, idVariante) + VALUES (?, ?); + `, +}; diff --git a/Utilidades/Constantes/mensajesProductos.js b/Utilidades/Constantes/mensajesProductos.js index 01b9a9b2..8f357e88 100644 --- a/Utilidades/Constantes/mensajesProductos.js +++ b/Utilidades/Constantes/mensajesProductos.js @@ -2,25 +2,62 @@ module.exports = { // 200 - OK CONSULTA_EXITOSA: { codigo: 200, - mensaje: "Lista de productos obtenida exitosamente.", + mensaje: 'Lista de productos obtenida exitosamente.', + }, + PRODUCTO_CREADO_EXITOSAMENTE: { + codigo: 200, + mensaje: 'Producto creado correctamente.', }, // 204 - No Content SIN_RESULTADOS: { - codigo: 200, - mensaje: "No se encontraron productos registrados para el cliente.", + codigo: 204, + mensaje: 'No se encontraron productos registrados para el cliente.', + }, + + // 400 - Bad Request + PARAMETROS_INVALIDOS: { + codigo: 400, + mensaje: 'Los parámetros proporcionados no son válidos o están incompletos.', + }, + LIMITE_OFFSET_INVALIDOS: { + codigo: 400, + mensaje: 'Los valores de límite u offset deben ser números positivos.', }, // 403 - Forbidden PERMISO_DENEGADO: { codigo: 403, - mensaje: "No tiene permiso para consultar productos de este cliente.", + mensaje: 'No tiene permiso para consultar productos de este cliente.', }, // 500 - Internal Server Error ERROR_CONSULTAR_PRODUCTOS: { codigo: 500, - mensaje: "Ocurrió un error al obtener la lista de productos.", + mensaje: 'Ocurrió un error al obtener la lista de productos.', + }, + ERROR_CREAR_PRODUCTO: { + codigo: 500, + mensaje: 'Ocurrió un error al crear el producto. Por favor, intente nuevamente más tarde.', + }, + ERROR_ENVIAR_IMAGENES_S3: { + codigo: 500, + mensaje: 'Ocurrió un error al subir las imágenes al servidor. Intente nuevamente.', + }, + ERROR_CREAR_VARIANTE: { + codigo: 500, + mensaje: + 'Ocurrió un error al crear una variante del producto. Verifique los datos de variantes.', + }, + ERROR_CREAR_OPCION: { + codigo: 500, + mensaje: 'Ocurrió un error al crear una opción para la variante. Revise las opciones enviadas.', + }, + + ERROR_CREAR_IMAGEN_VARIANTE: { + codigo: 500, + mensaje: + 'Ocurrió un error al asociar la imagen con la variante. Verifique los datos de las imágenes de variantes.', }, // 200 - OK diff --git a/Utilidades/Constantes/mensajesProveedores.js b/Utilidades/Constantes/mensajesProveedores.js new file mode 100644 index 00000000..20d8f3fa --- /dev/null +++ b/Utilidades/Constantes/mensajesProveedores.js @@ -0,0 +1,53 @@ +module.exports = { + // 200 - OK + PROVEEDOR_CREADO_EXITOSAMENTE: { + codigo: 200, + mensaje: 'El proveedor fue creado exitosamente.', + }, + CONSULTA_PROVEEDORES_EXITOSA: { + codigo: 200, + mensaje: 'Lista de proveedores obtenida exitosamente.', + }, + PROVEEDOR_OBTENIDO_EXITOSAMENTE: { + codigo: 200, + mensaje: 'Información del proveedor obtenida exitosamente.', + }, + + // 204 - No Content + LISTA_PROVEEDORES_VACIA: { + codigo: 204, + mensaje: 'No hay proveedores registrados actualmente.', + }, + + // 400 - Bad Request + DATOS_PROVEEDOR_INVALIDOS: { + codigo: 400, + mensaje: 'Los datos del proveedor proporcionados son inválidos o están incompletos.', + }, + + // 403 - Forbidden + ACCESO_NO_AUTORIZADO_PROVEEDORES: { + codigo: 403, + mensaje: 'No tiene permiso para consultar o modificar proveedores.', + }, + + // 404 - Not Found + PROVEEDOR_NO_ENCONTRADO: { + codigo: 404, + mensaje: 'No se encontró un proveedor con el ID proporcionado.', + }, + + // 500 - Internal Server Error + ERROR_CREAR_PROVEEDOR: { + codigo: 500, + mensaje: 'Ocurrió un error al intentar crear el proveedor.', + }, + ERROR_CONSULTAR_PROVEEDORES: { + codigo: 500, + mensaje: 'Ocurrió un error al obtener la lista de proveedores.', + }, + ERROR_CONSULTAR_PROVEEDOR: { + codigo: 500, + mensaje: 'Ocurrió un error al obtener la información del proveedor.', + }, +}; diff --git a/Utilidades/Constantes/rutas.js b/Utilidades/Constantes/rutas.js index 8e24d1de..61cca03a 100644 --- a/Utilidades/Constantes/rutas.js +++ b/Utilidades/Constantes/rutas.js @@ -36,11 +36,19 @@ module.exports = { PRODUCTOS: { BASE: '/productos', CONSULTAR_LISTA: '/consultar-lista', + CREAR: '/crear', ELIMINAR_PRODUCTO: '/eliminar', }, + PROVEEDORES: { + BASE: '/proveedores', + CONSULTAR_LISTA: '/consultar-lista', + CREAR: '/crear', + }, SETS_PRODUCTOS: { BASE: '/sets-productos', CONSULTAR_LISTA: '/consultar-lista', + CREAR: '/crear', + SUBIR_IMAGEN: '/subir-imagen', ELIMINAR_SET_PRODUCTOS: '/eliminar', }, CLIENTES: { diff --git a/Utilidades/Intermediarios/Validaciones/validarOpciones.js b/Utilidades/Intermediarios/Validaciones/validarOpciones.js new file mode 100644 index 00000000..8dc60a95 --- /dev/null +++ b/Utilidades/Intermediarios/Validaciones/validarOpciones.js @@ -0,0 +1,86 @@ +//RF26 Crea Producto - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF26 +/** + * Valida un conjunto de opciones para un producto. + * + * Esta función valida cada opción dentro del arreglo de opciones pasado como argumento. + * Se asegura de que los valores de las propiedades cumplan con los tipos y rangos especificados, + * y retorna un objeto de error si algún valor es inválido. + * + * @param {Array} opciones - Un arreglo de objetos que representan las opciones de un producto. + * @param {number} opciones[].cantidad - La cantidad de la opción, que debe ser un número entero positivo o cero. + * @param {string} opciones[].valorOpcion - El valor de la opción, que debe ser una cadena de texto de máximo 100 caracteres. + * @param {string} [opciones[].SKUautomatico] - El SKU automático de la opción, que debe ser una cadena de texto de máximo 50 caracteres, si se proporciona. + * @param {string} [opciones[].SKUcomercial] - El SKU comercial de la opción, que debe ser una cadena de texto de máximo 50 caracteres, si se proporciona. + * @param {number} opciones[].costoAdicional - El costo adicional de la opción, que debe ser un número positivo o cero. + * @param {number} opciones[].descuento - El descuento de la opción, que debe ser un número entre 0 y 100. + * @param {number} opciones[].estado - El estado de la opción, que debe ser 1 (activo) o 0 (inactivo). + * + * @returns {object|null} Un objeto con una propiedad `error` si hay un error de validación, o `null` si todas las opciones son válidas. + * @example + * const opciones = [ + * { + * cantidad: 5, + * valorOpcion: 'Tamaño M', + * SKUautomatico: 'SKU123', + * SKUcomercial: 'S123', + * costoAdicional: 15.50, + * descuento: 10, + * estado: 1 + * } + * ]; + * + * const resultado = validarOpciones(opciones); + * console.log(resultado); // null si todo está bien, o un objeto de error si algo es inválido + */ +module.exports = (opciones) => { + for (const opcion of opciones) { + // prettier-ignore + if ( + typeof opcion.cantidad !== 'number' + || opcion.cantidad < 0 + || !Number.isInteger(opcion.cantidad) + ) { + return { error: 'cantidad de la opción debe ser un número entero positivo o cero.' }; + } + + // prettier-ignore + if ( + !opcion.valorOpcion + || typeof opcion.valorOpcion !== 'string' + || opcion.valorOpcion.length > 100 + ) { + return { + error: 'valorOpcion es requerido y debe ser una cadena de texto de máximo 100 caracteres.', + }; + } + + // prettier-ignore + if ( + opcion.SKUautomatico + && (typeof opcion.SKUautomatico !== 'string' || opcion.SKUautomatico.length > 50) + ) { + return { error: 'SKUautomatico debe ser una cadena de texto de máximo 50 caracteres.' }; + } + // prettier-ignore + if ( + opcion.SKUcomercial + && (typeof opcion.SKUcomercial !== 'string' || opcion.SKUcomercial.length > 50) + ) { + return { error: 'SKUcomercial debe ser una cadena de texto de máximo 50 caracteres.' }; + } + + if (typeof opcion.costoAdicional !== 'number' || opcion.costoAdicional < 0) { + return { error: 'costoAdicional debe ser un número positivo o cero.' }; + } + + if (typeof opcion.descuento !== 'number' || opcion.descuento < 0 || opcion.descuento > 100) { + return { error: 'descuento debe ser un número entre 0 y 100.' }; + } + + if (typeof opcion.estado !== 'number' || (opcion.estado !== 0 && opcion.estado !== 1)) { + return { error: 'estado debe ser 1 (activo) o 0 (inactivo).' }; + } + } + + return null; +}; diff --git a/Utilidades/Intermediarios/Validaciones/validarProducto.js b/Utilidades/Intermediarios/Validaciones/validarProducto.js new file mode 100644 index 00000000..5738d072 --- /dev/null +++ b/Utilidades/Intermediarios/Validaciones/validarProducto.js @@ -0,0 +1,138 @@ +//RF26 Crea Producto - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF26 +/** + * Valida los campos de un producto. + * + * Esta función valida los diferentes campos de un objeto `producto` asegurándose de que cumplan con los tipos y restricciones especificados. Si algún campo no cumple con las condiciones, se devuelve un objeto de error con un mensaje específico. Si todos los campos son válidos, se retorna `null`. + * + * @param {object} producto - El objeto que representa un producto a validar. + * @param {number|null} producto.idProveedor - El ID del proveedor, debe ser un número entero positivo o `null`. + * @param {string} producto.nombreComun - El nombre común del producto, debe ser una cadena de texto de máximo 100 caracteres. + * @param {string|null} producto.nombreComercial - El nombre comercial del producto, debe ser una cadena de texto de máximo 150 caracteres o `null`. + * @param {string|null} producto.descripcion - Descripción del producto, debe ser una cadena de texto de máximo 1000 caracteres o `null`. + * @param {string|null} producto.marca - La marca del producto, debe ser una cadena de texto de máximo 100 caracteres o `null`. + * @param {string|null} producto.modelo - El modelo del producto, debe ser una cadena de texto de máximo 100 caracteres o `null`. + * @param {string|null} producto.tipoProducto - El tipo de producto, debe ser una cadena de texto de máximo 50 caracteres o `null`. + * @param {number|null} producto.precioPuntos - El precio en puntos, debe ser un número entero positivo o `null`. + * @param {number|null} producto.precioCliente - El precio para el cliente, debe ser un número mayor o igual a cero o `null`. + * @param {number|null} producto.precioVenta - El precio de venta, debe ser un número mayor o igual a cero o `null`. + * @param {number|null} producto.costo - El costo del producto, debe ser un número mayor o igual a cero o `null`. + * @param {number|null} producto.impuesto - El impuesto del producto, debe ser un número mayor o igual a cero o `null`. + * @param {number|null} producto.descuento - El descuento del producto, debe ser un número mayor o igual a cero o `null`. + * @param {number} producto.estado - El estado del producto, debe ser 0 (inactivo) o 1 (activo). + * @param {number} producto.envio - Indica si el envío está disponible, debe ser 0 (no disponible) o 1 (disponible). + * + * @returns {object|null} Un objeto con una propiedad `error` si hay un error de validación, o `null` si todos los campos son válidos. + * @example + * const producto = { + * idProveedor: 1, + * nombreComun: 'Producto X', + * nombreComercial: 'Producto X Comercial', + * descripcion: 'Descripción del producto', + * marca: 'Marca X', + * modelo: 'Modelo 123', + * tipoProducto: 'Tipo A', + * precioPuntos: 100, + * precioCliente: 200.50, + * precioVenta: 250, + * costo: 150, + * impuesto: 25, + * descuento: 10, + * estado: 1, + * envio: 1, + * }; + * + * const resultado = validarProducto(producto); + * console.log(resultado); // null si todo está bien, o un objeto de error si algo es inválido + */ +// prettier-ignore +module.exports = (producto) => { + if ( + producto.idProveedor !== null + && (typeof producto.idProveedor !== 'number' + || producto.idProveedor <= 0 + || !Number.isInteger(producto.idProveedor)) + ) { + return { error: 'idProveedor debe ser un número entero positivo o NULL.' }; + } + + if ( + !producto.nombreComun + || typeof producto.nombreComun !== 'string' + || producto.nombreComun.length > 100 + ) { + return { + error: 'nombreComun es requerido, debe ser una cadena de texto y no exceder 100 caracteres.', + }; + } + + if ( + producto.nombreComercial !== null + && (typeof producto.nombreComercial !== 'string' || producto.nombreComercial.length > 150) + ) { + return { + error: 'nombreComercial debe ser una cadena de texto o NULL y no exceder 150 caracteres.', + }; + } + + if ( + producto.descripcion !== null + && (typeof producto.descripcion !== 'string' || producto.descripcion.length > 1000) + ) { + return { + error: 'descripcion debe ser una cadena de texto o NULL y no exceder 1000 caracteres.', + }; + } + + if ( + producto.marca !== null + && (typeof producto.marca !== 'string' || producto.marca.length > 100) + ) { + return { error: 'marca debe ser una cadena de texto o NULL y no exceder 100 caracteres.' }; + } + + if ( + producto.modelo !== null + && (typeof producto.modelo !== 'string' || producto.modelo.length > 100) + ) { + return { error: 'modelo debe ser una cadena de texto o NULL y no exceder 100 caracteres.' }; + } + + if ( + producto.tipoProducto !== null + && (typeof producto.tipoProducto !== 'string' || producto.tipoProducto.length > 50) + ) { + return { + error: 'tipoProducto debe ser una cadena de texto o NULL y no exceder 50 caracteres.', + }; + } + + const camposNumericos = [ + 'precioPuntos', + 'precioCliente', + 'precioVenta', + 'costo', + 'impuesto', + 'descuento', + ]; + + for (const campo of camposNumericos) { + if ( + producto[campo] !== null + && (typeof producto[campo] !== 'number' + || (campo === 'precioPuntos' && !Number.isInteger(producto[campo])) + || (campo !== 'precioPuntos' && producto[campo] < 0)) + ) { + return { error: `${campo} debe ser un número válido y mayor o igual a cero.` }; + } + } + + if (producto.estado !== undefined && producto.estado !== 0 && producto.estado !== 1) { + return { error: 'estado debe ser 0 (inactivo) o 1 (activo).' }; + } + + if (producto.envio !== undefined && producto.envio !== 0 && producto.envio !== 1) { + return { error: 'envio debe ser 0 (no disponible) o 1 (disponible).' }; + } + + return null; +}; diff --git a/Utilidades/Intermediarios/Validaciones/validarProveedor.js b/Utilidades/Intermediarios/Validaciones/validarProveedor.js new file mode 100644 index 00000000..2618b3dc --- /dev/null +++ b/Utilidades/Intermediarios/Validaciones/validarProveedor.js @@ -0,0 +1,84 @@ +//RF26 Crea Producto - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF26 +/** + * Valida los campos de un proveedor. + * + * Esta función verifica que los campos del objeto `proveedor` cumplan con los requisitos de tipo, longitud y formato. + * Si algún campo no es válido, devuelve un objeto con un mensaje de error específico. + * Si todos los campos son válidos, retorna `null`. + * + * @param {object} proveedor - Objeto que representa al proveedor a validar. + * @param {string} proveedor.nombre - Nombre del proveedor (obligatorio, máximo 100 caracteres). + * @param {string} [proveedor.nombreCompania] - Nombre de la compañía (opcional, máximo 150 caracteres). + * @param {string} [proveedor.telefonoContacto] - Teléfono de contacto (opcional, máximo 20 caracteres). + * @param {string} [proveedor.correoContacto] - Correo electrónico de contacto (opcional, válido y máximo 100 caracteres). + * @param {string} [proveedor.direccion] - Dirección del proveedor (opcional, máximo 200 caracteres). + * @param {string} [proveedor.codigoPostal] - Código postal (opcional, máximo 20 caracteres). + * @param {string} [proveedor.pais] - País del proveedor (opcional, máximo 50 caracteres). + * @param {number} proveedor.estado - Estado del proveedor: 1 (activo) o 0 (inactivo). + * + * @returns {{ error: string } | null} Retorna un objeto con la propiedad `error` si hay un error de validación, o `null` si todo es válido. + */ +module.exports = (proveedor) => { + /** + * Verifica si un texto es válido (tipo string, no vacío, dentro del límite de caracteres). + * + * @param {string} valor - Texto a validar. + * @param {number} max - Longitud máxima permitida. + * @returns {boolean} `true` si es válido, `false` en caso contrario. + */ + // prettier-ignore + const esTextoValido = (valor, max) => typeof valor === 'string' && valor.trim().length > 0 && valor.trim().length <= max; + + if (!esTextoValido(proveedor.nombre, 100)) { + return { + error: 'nombre es obligatorio y debe ser una cadena de texto de máximo 100 caracteres.', + }; + } + + if (proveedor.nombreCompania != null && !esTextoValido(proveedor.nombreCompania, 150)) { + return { + error: 'nombreCompania debe ser una cadena de texto no vacía de máximo 150 caracteres.', + }; + } + + if (proveedor.telefonoContacto != null && !esTextoValido(proveedor.telefonoContacto, 20)) { + return { + error: 'telefonoContacto debe ser una cadena de texto no vacía de máximo 20 caracteres.', + }; + } + + // prettier-ignore + if (proveedor.correoContacto != null) { + const correo = proveedor.correoContacto.trim(); + const regexCorreo = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if ( + typeof correo !== 'string' + || correo.length === 0 + || correo.length > 100 + || !regexCorreo.test(correo) + ) { + return { + error: + 'correoContacto debe ser un correo electrónico válido y con un máximo de 100 caracteres.', + }; + } + } + + if (proveedor.direccion != null && !esTextoValido(proveedor.direccion, 200)) { + return { error: 'direccion debe ser una cadena de texto no vacía de máximo 200 caracteres.' }; + } + + if (proveedor.codigoPostal != null && !esTextoValido(proveedor.codigoPostal, 20)) { + return { error: 'codigoPostal debe ser una cadena de texto no vacía de máximo 20 caracteres.' }; + } + + if (proveedor.pais != null && !esTextoValido(proveedor.pais, 50)) { + return { error: 'pais debe ser una cadena de texto no vacía de máximo 50 caracteres.' }; + } + + if (typeof proveedor.estado !== 'number' || (proveedor.estado !== 1 && proveedor.estado !== 0)) { + return { error: 'estado debe ser 1 (activo) o 0 (inactivo).' }; + } + + return null; +}; diff --git a/Utilidades/Intermediarios/Validaciones/validarVariante.js b/Utilidades/Intermediarios/Validaciones/validarVariante.js new file mode 100644 index 00000000..b8646350 --- /dev/null +++ b/Utilidades/Intermediarios/Validaciones/validarVariante.js @@ -0,0 +1,44 @@ +//RF26 Crea Producto - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF26 +/** + * Valida los campos de una variante. + * + * Esta función valida los diferentes campos de un objeto `variante` asegurándose de que cumplan con los tipos y restricciones especificadas. Si algún campo no cumple con las condiciones, se devuelve un objeto de error con un mensaje específico. Si todos los campos son válidos, se retorna `null`. + * + * @param {object} variante - El objeto que representa una variante a validar. + * @param {string} variante.nombreVariante - El nombre de la variante, debe ser una cadena de texto de máximo 100 caracteres. + * @param {string|null} variante.descripcion - La descripción de la variante, debe ser una cadena de texto de máximo 1000 caracteres o `null`. + * + * @returns {object|null} Un objeto con una propiedad `error` si hay un error de validación, o `null` si todos los campos son válidos. + * @example + * const variante = { + * nombreVariante: 'Tamaño L', + * descripcion: 'Variante para talla grande', + * }; + * + * const resultado = validarVariante(variante); + * console.log(resultado); // null si todo está bien, o un objeto de error si algo es inválido + */ +// prettier-ignore +module.exports = (variante) => { + if ( + !variante.nombreVariante + || typeof variante.nombreVariante !== 'string' + || variante.nombreVariante.length > 100 + ) { + return { + error: + 'nombreVariante es requerido, debe ser una cadena de texto y no exceder 100 caracteres.', + }; + } + + if ( + variante.descripcion !== null + && (typeof variante.descripcion !== 'string' || variante.descripcion.length > 1000) + ) { + return { + error: 'descripcion debe ser una cadena de texto o NULL y no exceder 1000 caracteres.', + }; + } + + return null; +}; diff --git a/Utilidades/Servicios/enviarS3.js b/Utilidades/Servicios/enviarS3.js index e7cdc54b..3cf09461 100644 --- a/Utilidades/Servicios/enviarS3.js +++ b/Utilidades/Servicios/enviarS3.js @@ -1,7 +1,11 @@ const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3'); const s3 = new S3Client({ - region: 'us-east-1', + region: process.env.AWS_REGION, + credentials: { + accessKeyId: process.env.AWS_ACCESS_KEY_ID, + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, + }, }); /** diff --git a/app.js b/app.js index cc9419ea..20abb017 100644 --- a/app.js +++ b/app.js @@ -17,6 +17,7 @@ const rutasAutenticacion = require('@altertex/aut/rutas/indexAutenticacion.route const rutasUsuarios = require('@altertex/usu/rutas/indexUsuarios.routes'); const rutasCategorias = require('@altertex/cat/rutas/indexCategorias.routes'); const rutasProductos = require('@altertex/pro/rutas/indexProductos.routes'); +const rutasProveedores = require('@altertex/prove/rutas/indexProveedores.routes'); const rutasSetsProductos = require('@altertex/setspro/rutas/indexSetsProductos.routes'); const rutasEmpleados = require('@altertex/emp/rutas/indexEmpleados.routes'); const rutasClientes = require('@altertex/cli/rutas/indexClientes.routes'); @@ -45,6 +46,7 @@ cronCuotas.start(); app.use(RUTAS.API, rutasAutenticacion); app.use(RUTAS.API, rutasUsuarios); app.use(RUTAS.API, rutasProductos); +app.use(RUTAS.API, rutasProveedores); app.use(RUTAS.API, rutasSetsProductos); app.use(RUTAS.API, rutasEmpleados); app.use(RUTAS.API, rutasClientes); diff --git a/jsconfig.json b/jsconfig.json index ee8f430a..9e8820a1 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -32,6 +32,7 @@ "@altertex/util/bd/*": ["Utilidades/BaseDeDatos/*"], "@altertex/util/const/*": ["Utilidades/Constantes/*"], "@altertex/util/inter/*": ["Utilidades/Intermediarios/*"], + "@altertex/util/vali/*": ["Utilidades/Intermediarios/Validaciones/*"], "@altertex/util/ser/*": ["Utilidades/Servicios/*"], "@altertex/rol/*": ["Roles/*"], "@altertex/rol/ctrl/*": ["Roles/Controladores/*"], @@ -45,6 +46,12 @@ "@altertex/pro/repos/*": ["Productos/Datos/Repositorios/*"], "@altertex/pro/rutas/*": ["Productos/Rutas/*"], "@altertex/pro/rutasInd/*": ["Productos/Rutas/RutasIndividuales/*"], + "@altertex/prove/*": ["Proveedores/*"], + "@altertex/prove/ctrl/*": ["Proveedores/Controladores/*"], + "@altertex/prove/datos/*": ["Proveedores/Datos/*"], + "@altertex/prove/repos/*": ["Proveedores/Datos/Repositorios/*"], + "@altertex/prove/rutas/*": ["Proveedores/Rutas/*"], + "@altertex/prove/rutasInd/*": ["Proveedores/Rutas/RutasIndividuales/*"], "@altertex/cuota/*": ["Cuotas/*"], "@altertex/cuota/ctrl/*": ["Cuotas/Controladores/*"], "@altertex/cuota/datos/*": ["Cuotas/Datos/*"], diff --git a/package.json b/package.json index c01a7cdc..9eabb5a3 100644 --- a/package.json +++ b/package.json @@ -82,6 +82,7 @@ "@altertex/util/bd": "Utilidades/BaseDeDatos/", "@altertex/util/const": "Utilidades/Constantes/", "@altertex/util/inter": "Utilidades/Intermediarios/", + "@altertex/util/vali": "Utilidades/Intermediarios/Validaciones", "@altertex/rol": "Roles", "@altertex/rol/ctrl": "Roles/Controladores", "@altertex/rol/datos": "Roles/Datos", @@ -98,6 +99,12 @@ "@altertex/pro/repos": "Productos/Datos/Repositorios", "@altertex/pro/rutas": "Productos/Rutas/", "@altertex/pro/rutasInd": "Productos/Rutas/RutasIndividuales", + "@altertex/prove": "Proveedores", + "@altertex/prove/ctrl": "Proveedores/Controladores/", + "@altertex/prove/datos": "Proveedores/Datos/", + "@altertex/prove/repos": "Proveedores/Datos/Repositorios", + "@altertex/prove/rutas": "Proveedores/Rutas/", + "@altertex/prove/rutasInd": "Proveedores/Rutas/RutasIndividuales", "@altertex/cat": "Categorias/", "@altertex/cat/ctrl": "Categorias/Controladores/", "@altertex/cat/datos": "Categorias/Datos/", From aaffc4fddbbf3bc5722f85789e864f258fda1e35 Mon Sep 17 00:00:00 2001 From: Diego Alfaro Pinto Date: Tue, 20 May 2025 12:39:57 -0600 Subject: [PATCH 008/116] feat: agregar funcionalidad para consultar la informacion del producto --- .../Controladores/leerProducto.controller.js | 39 ++++++++ .../Repositorios/repositorioLeerProducto.js | 27 ++++++ .../RutasIndividuales/leerProductos.routes.js | 14 +++ Productos/Rutas/indexProductos.routes.js | 7 +- Utilidades/Constantes/consultasProductos.js | 93 ++++++++++++++----- Utilidades/Constantes/mensajesProductos.js | 27 +++++- Utilidades/Constantes/rutas.js | 1 + 7 files changed, 181 insertions(+), 27 deletions(-) create mode 100644 Productos/Controladores/leerProducto.controller.js create mode 100644 Productos/Datos/Repositorios/repositorioLeerProducto.js create mode 100644 Productos/Rutas/RutasIndividuales/leerProductos.routes.js diff --git a/Productos/Controladores/leerProducto.controller.js b/Productos/Controladores/leerProducto.controller.js new file mode 100644 index 00000000..888337d0 --- /dev/null +++ b/Productos/Controladores/leerProducto.controller.js @@ -0,0 +1,39 @@ +const MENSAJES = require('@altertex/util/const/mensajesProductos'); +const repositorio = require('@altertex/pro/repos/repositorioLeerProducto'); +// RF[28] Leer producto - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF28] + +/** + * Controlador para leer la información de un producto específico. + * + * Este endpoint espera que el `idProducto` se encuentre en el cuerpo de la solicitud + * y que el `idCliente` esté disponible en el objeto `req.user.clienteSeleccionado`. + * + * @async + * @function leerProducto + * @param {Express.Request} req - Objeto de solicitud de Express. + * @param {object} req.body - Cuerpo de la solicitud. + * @param {string|number} req.body.idProducto - ID del producto a consultar. + * @param {object} req.user - Usuario autenticado con un cliente seleccionado. + * @param {string|number} req.user.clienteSeleccionado - ID del cliente asociado. + * @param {Express.Response} res - Objeto de respuesta de Express. + * @returns {Promise} Retorna una respuesta HTTP con la información del producto o un mensaje de error. + * @throws {Error} Lanza un error si `idProducto` no es válido o si ocurre un error al consultar el repositorio. + */ +exports.leerProducto = async (req, res) => { + const idProducto = req.body.idProducto; + const idCliente = req.user.clienteSeleccionado; + + if (!idProducto) { + throw new Error(MENSAJES.ID_INVALIDO.mensaje); + } + + try { + const infoProducto = await repositorio.leerProducto(idProducto, idCliente); + return res.status(MENSAJES.LEER_PRODUCTO_EXITO.codigo).json({ + mensaje: MENSAJES.LEER_PRODUCTO_EXITO.mensaje, + infoProducto, + }); + } catch (error) { + return res.status(MENSAJES.ERROR_LEER_PRODUCTO.codigo).json({ mensaje: error.message }); + } +}; \ No newline at end of file diff --git a/Productos/Datos/Repositorios/repositorioLeerProducto.js b/Productos/Datos/Repositorios/repositorioLeerProducto.js new file mode 100644 index 00000000..7c306d02 --- /dev/null +++ b/Productos/Datos/Repositorios/repositorioLeerProducto.js @@ -0,0 +1,27 @@ +const CONSULTAS = require('@altertex/util/const/consultasProductos'); +const MENSAJES = require('@altertex/util/const/mensajesProductos'); +const correrQuery = require('@altertex/util/ser/correrQuery'); + +/** + * Lee la información de un producto específico para un cliente dado. + * + * @async + * @function leerProducto + * @param {number|string} idProducto - El identificador del producto a consultar. + * @param {number|string} idCliente - El identificador del cliente que realiza la consulta. + * @returns {Promise} Retorna una promesa que resuelve con los datos del producto si se encuentra. + * @throws {Error} Lanza un error si el producto no se encuentra o si ocurre un error durante la consulta. + */ +exports.leerProducto = async (idProducto, idCliente) => { + try { + const resultado = await correrQuery(CONSULTAS.LEER_PRODUCTO, [idProducto, idCliente]); + + if (resultado.length === 0) { + throw new Error(MENSAJES.PRODUCTO_NO_ENCONTRADO.mensaje); + } + + return resultado; + } catch (error) { + throw new Error(error.message); + } +}; diff --git a/Productos/Rutas/RutasIndividuales/leerProductos.routes.js b/Productos/Rutas/RutasIndividuales/leerProductos.routes.js new file mode 100644 index 00000000..d11e4424 --- /dev/null +++ b/Productos/Rutas/RutasIndividuales/leerProductos.routes.js @@ -0,0 +1,14 @@ +const express = require('express'); +const ruteador = express.Router(); +const PERMISOS = require('@altertex/util/const/permisos'); +const RUTAS = require('@altertex/util/const/rutas'); +const revisarApiKey = require('@altertex/util/inter/revisarApiKey'); +const autorizarToken = require('@altertex/util/inter/autorizarToken'); +const verificarPermisos = require('@altertex/util/inter/verificarPermisos'); +const validarYSanitizar = require('@altertex/util/inter/validarYSanitizar'); +const controlador = require('@altertex/pro/ctrl/leerProducto.controller'); +// RF[28] Leer producto - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF28] + +ruteador.get(RUTAS.PRODUCTOS.LEER, revisarApiKey(), autorizarToken, verificarPermisos(PERMISOS.LEER_PRODUCTO), validarYSanitizar, controlador.leerProducto); + +module.exports = ruteador; \ No newline at end of file diff --git a/Productos/Rutas/indexProductos.routes.js b/Productos/Rutas/indexProductos.routes.js index 47a7202e..e10c46a5 100644 --- a/Productos/Rutas/indexProductos.routes.js +++ b/Productos/Rutas/indexProductos.routes.js @@ -1,8 +1,9 @@ const express = require('express'); const ruteador = express.Router(); const rutaConsultarLista = require('@altertex/pro/rutasInd/consultarProductos.routes'); -const rutaEliminar = require("@altertex/pro/rutasInd/eliminarProducto.routes"); +const rutaEliminar = require('@altertex/pro/rutasInd/eliminarProducto.routes'); const rutaCrearProducto = require('@altertex/pro/rutasInd/crearProducto.routes'); +const rutasLeerProducto = require('@altertex/pro/rutasInd/leerProductos.routes'); const RUTAS = require('@altertex/util/const/rutas'); @@ -10,7 +11,9 @@ const RUTAS = require('@altertex/util/const/rutas'); ruteador.use(RUTAS.PRODUCTOS.BASE, rutaConsultarLista); //RF26 Crea Producto - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF26 ruteador.use(RUTAS.PRODUCTOS.BASE, rutaCrearProducto); -// RF[30] Eliminar Producto - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF30] +// RF[30] Eliminar Producto - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF30] ruteador.use(RUTAS.PRODUCTOS.BASE, rutaEliminar); +// RF[28] Leer producto - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF28] +ruteador.use(RUTAS.PRODUCTOS.BASE, rutasLeerProducto); module.exports = ruteador; \ No newline at end of file diff --git a/Utilidades/Constantes/consultasProductos.js b/Utilidades/Constantes/consultasProductos.js index 81b5df4c..abbdca58 100644 --- a/Utilidades/Constantes/consultasProductos.js +++ b/Utilidades/Constantes/consultasProductos.js @@ -1,29 +1,78 @@ module.exports = { OBTENER_LISTA: ` - SELECT p.idProducto, p.nombreComun, p.precioVenta, p.estado, i.urlImagen - FROM producto p - JOIN imagen_producto ip ON p.idProducto = ip.idProducto - JOIN imagen i ON ip.idImagen = i.idImagen - WHERE i.tipoImagen = "Imagen Producto" - AND p.idCliente = ?; - `, + SELECT p.idProducto, p.nombreComun, p.precioVenta, p.estado, i.urlImagen + FROM producto p + JOIN imagen_producto ip ON p.idProducto = ip.idProducto + JOIN imagen i ON ip.idImagen = i.idImagen + WHERE i.tipoImagen = "Imagen Producto" + AND p.idCliente = ?; + `, CREAR: ` - INSERT INTO producto ( - idCliente, idProveedor, nombreComun, nombreComercial, descripcion, - marca, modelo, tipoProducto, precioPuntos, precioCliente, - precioVenta, costo, impuesto, descuento, estado, envio - ) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); - `, + INSERT INTO producto (idCliente, idProveedor, nombreComun, nombreComercial, descripcion, + marca, modelo, tipoProducto, precioPuntos, precioCliente, + precioVenta, costo, impuesto, descuento, estado, envio) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); + `, CREAR_IMAGEN_PRODUCTO: ` - INSERT INTO imagen_producto (idImagen, idProducto) - VALUES (?, ?); - `, + INSERT INTO imagen_producto (idImagen, idProducto) + VALUES (?, ?); + `, CREAR_DATOS_ENVIO: ` - INSERT INTO datos_envio (idProducto, peso, longitud, ancho, altura, volumen, tipoPaquete) - VALUES (?, ?, ?, ?, ?, ?,?); - `, + INSERT INTO datos_envio (idProducto, peso, longitud, ancho, altura, volumen, tipoPaquete) + VALUES (?, ?, ?, ?, ?, ?, ?); + `, - ELIMINAR_PRODUCTOS: - "DELETE FROM producto WHERE idProducto IN (?)", + ELIMINAR_PRODUCTOS: + 'DELETE FROM producto WHERE idProducto IN (?)', + + LEER_PRODUCTO: ` + SELECT JSON_OBJECT( + 'idProducto', p.idProducto, + 'idProveedor', p.idProveedor, + 'nombreComun', p.nombreComun, + 'nombreComercial', p.nombreComercial, + 'marca', p.marca, + 'modelo', p.modelo, + 'tipoProducto', p.tipoProducto, + 'precioPuntos', p.precioPuntos, + 'precioCliente', p.precioCliente, + 'precioVenta', p.precioVenta, + 'costo', p.costo, + 'impuesto', p.impuesto, + 'descuento', p.descuento, + 'estado', p.estado, + 'envio', p.envio, + 'nombreProveedor', pr.nombre, + 'variantes', (SELECT JSON_ARRAYAGG( + JSON_OBJECT( + 'idVariante', v.idVariante, + 'nombreVariante', v.nombreVariante, + 'descripcion', v.descripcion, + 'opciones', (SELECT JSON_ARRAYAGG( + JSON_OBJECT( + 'cantidad', o.cantidad, + 'valorOpcion', + o.valorOpcion, + 'SKUautomatico', + o.SKUautomatico, + 'SKUcomercial', + o.SKUcomercial, + 'costoAdicional', + o.costoAdicional, + 'descuento', o.descuento, + 'estado', o.estado + ) + ) + FROM opcion o + WHERE o.idVariante = v.idVariante) + ) + ) + FROM variante v + WHERE v.idProducto = p.idProducto) + ) AS producto + FROM producto p + LEFT JOIN proveedor pr ON p.idProveedor = pr.idProveedor + WHERE p.idProducto = ? + AND p.idCliente = ?; + `, }; diff --git a/Utilidades/Constantes/mensajesProductos.js b/Utilidades/Constantes/mensajesProductos.js index 8f357e88..1892c23f 100644 --- a/Utilidades/Constantes/mensajesProductos.js +++ b/Utilidades/Constantes/mensajesProductos.js @@ -63,12 +63,33 @@ module.exports = { // 200 - OK RESPUESTA_ELIMINAR_PRODUCTO_EXITOSA: { codigo: 200, - mensaje: "Producto eliminado exitosamente.", + mensaje: 'Producto eliminado exitosamente.', }, - + // 500 - Internal Server Error RESPUESTA_ERROR_GENERAL: { codigo: 500, - mensaje: "Ocurrió un error al procesar la solicitud.", + mensaje: 'Ocurrió un error al procesar la solicitud.', + }, + //LEER PRODUCTO + ERROR_LEER_PRODUCTO: { + codigo: 400, + mensaje: 'Ocurrió un error al obtener la informacion del producto.', + }, + LEER_PRODUCTO_EXITO: { + codigo: 200, + mensaje: 'Lista de productos consultada exitosamente.', + }, + ID_INVALIDO: { + codigo: 400, + mensaje: 'No se proporciono el id del producto.', + }, + ERROR_OBTENIENDO_INFORMACION: { + codigo: 400, + mensaje: 'Error obteniendo informacion del producto.', + }, + PRODUCTO_NO_ENCONTRADO: { + codigo: 400, + mensaje: 'El producto solicitado no existe.', }, }; diff --git a/Utilidades/Constantes/rutas.js b/Utilidades/Constantes/rutas.js index f2f43794..41dcd713 100644 --- a/Utilidades/Constantes/rutas.js +++ b/Utilidades/Constantes/rutas.js @@ -38,6 +38,7 @@ module.exports = { CONSULTAR_LISTA: '/consultar-lista', CREAR: '/crear', ELIMINAR_PRODUCTO: '/eliminar', + LEER: '/leer-producto' }, PROVEEDORES: { BASE: '/proveedores', From 96babb056c410e3168932eb7b76151e3f0ccf708 Mon Sep 17 00:00:00 2001 From: DiegoGarciaPadilla Date: Thu, 22 May 2025 16:34:48 -0600 Subject: [PATCH 009/116] feat: crear endpoint --- .../Controladores/crearEvento.controller.js | 36 ++++++++ .../Repositorios/repositorioCrearEvento.js | 29 +++++++ .../RutasIndividuales/crearEvento.routes.js | 83 +++++++++++++++++++ Eventos/Rutas/indexEventos.routes.js | 2 + Utilidades/Constantes/consultasEventos.js | 7 ++ Utilidades/Constantes/mensajesEventos.js | 4 - 6 files changed, 157 insertions(+), 4 deletions(-) create mode 100644 Eventos/Controladores/crearEvento.controller.js create mode 100644 Eventos/Datos/Repositorios/repositorioCrearEvento.js create mode 100644 Eventos/Rutas/RutasIndividuales/crearEvento.routes.js diff --git a/Eventos/Controladores/crearEvento.controller.js b/Eventos/Controladores/crearEvento.controller.js new file mode 100644 index 00000000..8de3ca98 --- /dev/null +++ b/Eventos/Controladores/crearEvento.controller.js @@ -0,0 +1,36 @@ +// RF36 - Crear Evento - [https://codeandco-wiki.netlify.app/docs/next/proyectos/textiles/documentacion/requisitos/RF36] + +const repositorio = require('@altertex/eve/repos/repositorioCrearEvento'); +const MENSAJES_EVENTOS = require('@altertex/util/const/mensajesEventos'); + +/** + * Controlador para crear un nuevo evento. + * + * @param {object} req - Objeto de solicitud de Express. + * @param {object} res - Objeto de respuesta de Express. + * + * @returns {object} - Respuesta JSON con el resultado de la operación. + */ +exports.crearEvento = (req, res) => { + const { idCliente, nombre, descripcion, puntos, multiplicador, periodoRenovacion, renovacion } = + req.body; + + // Validar los datos de entrada + const nuevoEvento = { + idCliente, + nombre, + descripcion, + puntos, + multiplicador, + periodoRenovacion, + renovacion, + }; + + // Validación de datos + if (repositorio.crearEvento(nuevoEvento)) { + return res.status(201).json({ + codigo: MENSAJES_EVENTOS.EVENTO_CREADO.codigo, + mensaje: MENSAJES_EVENTOS.EVENTO_CREADO.mensaje, + }); + } +}; diff --git a/Eventos/Datos/Repositorios/repositorioCrearEvento.js b/Eventos/Datos/Repositorios/repositorioCrearEvento.js new file mode 100644 index 00000000..8e685629 --- /dev/null +++ b/Eventos/Datos/Repositorios/repositorioCrearEvento.js @@ -0,0 +1,29 @@ +// RF36 - Crear Evento - [https://codeandco-wiki.netlify.app/docs/next/proyectos/textiles/documentacion/requisitos/RF36] + +const db = require('@altertex/util/bd/db'); +const CONSULTAS_EVENTOS = require('@altertex/util/const/consultasEventos'); + +/** + * Crea un nuevo evento en la base de datos + * @function crearEvento + * @param {object} evento - Objeto que contiene los datos del evento a crear + * @param {number} evento.idCliente - ID del cliente asociado al evento + * @param {string} evento.nombre - Nombre del evento + * @param {string} evento.descripcion - Descripción del evento + * @param {number} evento.puntos - Puntos asociados al evento + * @param {number} evento.multiplicador - Multiplicador del evento + * @param {number} evento.periodoRenovacion - Periodo de renovación del evento + * @param {number} evento.renovacion - Renovación del evento + * @returns {object} - Resultado de la operación de creación + */ +exports.crearEvento = async ({ + idCliente, + nombre, + descripcion, + puntos, + multiplicador, + periodoRenovacion, + renovacion, +}) => { + return true; +}; diff --git a/Eventos/Rutas/RutasIndividuales/crearEvento.routes.js b/Eventos/Rutas/RutasIndividuales/crearEvento.routes.js new file mode 100644 index 00000000..cd47f9b4 --- /dev/null +++ b/Eventos/Rutas/RutasIndividuales/crearEvento.routes.js @@ -0,0 +1,83 @@ +// RF36 - Crear Evento - [https://codeandco-wiki.netlify.app/docs/next/proyectos/textiles/documentacion/requisitos/RF36] +const express = require('express'); +const ruteador = express.Router(); +const controlador = require('@altertex/eve/ctrl/crearEvento.controller'); +const revisarApiKey = require('@altertex/util/inter/revisarApiKey'); +const autorizarToken = require('@altertex/util/inter/autorizarToken'); +const validarYSanitizar = require('@altertex/util/inter/validarYSanitizar'); +const verificarPermisos = require('@altertex/util/inter/verificarPermisos'); + +const PERMISOS = require('@altertex/util/const/permisos'); +const RUTAS = require('@altertex/util/const/rutas'); + +/** + * @swagger + * /api/eventos/crear: + * post: + * summary: Crea un nuevo evento. + * description: Este endpoint permite crear un nuevo evento en el sistema. + * tags: [Eventos] + * security: + * - ApiKeyAuth: [] + * - BearerAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * idCliente: + * type: string + * description: ID del cliente asociado al evento. + * example: 101 + * nombre: + * type: string + * description: Nombre del evento. + * example: Evento de prueba + * descripcion: + * type: string + * description: Descripción del evento. + * example: Este es un evento de prueba. + * puntos: + * type: number + * format: double + * description: Puntos otorgados por el evento. + * example: 10.5 + * multiplicador: + * type: number + * format: double + * description: Multiplicador de puntos del evento. + * example: 1.5 + * periodoRenovacion: + * type: string + * description: Periodo de renovación del evento. + * example: mensual + * renovacion: + * type: boolean + * description: Indica si el evento se renueva automáticamente. + * example: true + * responses: + * 200: + * description: Evento creado exitosamente. + * 400: + * description: Solicitud incorrecta. + * 401: + * description: No autorizado. + * 403: + * description: Prohibido. + * 404: + * description: No encontrado. + * 500: + * description: Error interno del servidor. + */ +ruteador.post( + RUTAS.EVENTOS.CREAR, + validarYSanitizar, + revisarApiKey(), + autorizarToken, + verificarPermisos(PERMISOS.CREAR_EVENTO), + controlador.crearEvento +); + +module.exports = ruteador; diff --git a/Eventos/Rutas/indexEventos.routes.js b/Eventos/Rutas/indexEventos.routes.js index e6c02353..e3aef2a4 100644 --- a/Eventos/Rutas/indexEventos.routes.js +++ b/Eventos/Rutas/indexEventos.routes.js @@ -1,5 +1,6 @@ const express = require('express'); const ruteador = express.Router(); +const crearEvento = require('@altertex/eve/rutasInd/crearEvento.routes'); const rutasConsultarListaEventos = require('@altertex/eve/rutasInd/consultarListaEventos.routes'); const rutasConsultarEvento = require('@altertex/eve/rutasInd/consultarEvento.routes'); const rutasEliminarEvento = require('@altertex/eve/rutasInd/eliminarEvento.routes'); @@ -11,6 +12,7 @@ const rutasEliminarEvento = require('@altertex/eve/rutasInd/eliminarEvento.route const RUTAS = require('@altertex/util/const/rutas'); // Configuración de las rutas específicas para eventos +ruteador.use(RUTAS.EVENTOS.BASE, crearEvento); ruteador.use(RUTAS.EVENTOS.BASE, rutasConsultarListaEventos); ruteador.use(RUTAS.EVENTOS.BASE, rutasEliminarEvento); ruteador.use(RUTAS.EVENTOS.BASE, rutasConsultarEvento); diff --git a/Utilidades/Constantes/consultasEventos.js b/Utilidades/Constantes/consultasEventos.js index c9d54bcc..022c9eca 100644 --- a/Utilidades/Constantes/consultasEventos.js +++ b/Utilidades/Constantes/consultasEventos.js @@ -1,4 +1,11 @@ module.exports = { + CREAR_EVENTO: ` + INSERT INTO evento (idCliente, nombre, descripcion, puntos, multiplicador, periodoRenovacion, renovacion) + VALUES (?, ?, ?, ?, ?, ?, ?); + `, + VERIFICAR_CLIENTE: ` + SELECT idCliente FROM cliente WHERE idCliente = ?; + `, OBTENER_LISTA_EVENTOS: ` SELECT e.idEvento, diff --git a/Utilidades/Constantes/mensajesEventos.js b/Utilidades/Constantes/mensajesEventos.js index 60bb5ef7..d6a3b265 100644 --- a/Utilidades/Constantes/mensajesEventos.js +++ b/Utilidades/Constantes/mensajesEventos.js @@ -1,9 +1,5 @@ module.exports = { // 201 - Creado - CATEGORIA_CREADA: { - codigo: 201, - mensaje: 'Categoría creada correctamente.', - }, EVENTO_CREADO: { codigo: 201, mensaje: 'Evento creado correctamente.', From fa256841954489da70ee3d1644bc28a59e76c7fe Mon Sep 17 00:00:00 2001 From: DiegoGarciaPadilla Date: Fri, 23 May 2025 11:17:54 -0600 Subject: [PATCH 010/116] feat: crear evento --- .../Controladores/crearEvento.controller.js | 52 ++++++++++++------- .../Repositorios/repositorioCrearEvento.js | 23 +++++++- 2 files changed, 55 insertions(+), 20 deletions(-) diff --git a/Eventos/Controladores/crearEvento.controller.js b/Eventos/Controladores/crearEvento.controller.js index 8de3ca98..f4d37478 100644 --- a/Eventos/Controladores/crearEvento.controller.js +++ b/Eventos/Controladores/crearEvento.controller.js @@ -2,35 +2,51 @@ const repositorio = require('@altertex/eve/repos/repositorioCrearEvento'); const MENSAJES_EVENTOS = require('@altertex/util/const/mensajesEventos'); +const { parse } = require('dotenv'); /** * Controlador para crear un nuevo evento. * * @param {object} req - Objeto de solicitud de Express. + * @param {object} req.body - Cuerpo de la solicitud que contiene los datos del evento. + * @param {string} req.body.idCliente - ID del cliente asociado al evento. + * @param {string} req.body.nombre - Nombre del evento. + * @param {string} req.body.descripcion - Descripción del evento. + * @param {number} req.body.puntos - Puntos otorgados por el evento. + * @param {number} req.body.multiplicador - Multiplicador de puntos del evento. + * @param {string} req.body.periodoRenovacion - Periodo de renovación del evento. + * @param {boolean} req.body.renovacion - Indica si el evento se renueva automáticamente. * @param {object} res - Objeto de respuesta de Express. - * + * * @returns {object} - Respuesta JSON con el resultado de la operación. */ exports.crearEvento = (req, res) => { - const { idCliente, nombre, descripcion, puntos, multiplicador, periodoRenovacion, renovacion } = - req.body; + try { + + const { idCliente, nombre, descripcion, puntos, multiplicador, periodoRenovacion, renovacion } = req.body; + + // Validar los datos de entrada + const nuevoEvento = { + idCliente: parseInt(idCliente, 10), + nombre, + descripcion, + puntos: parseFloat(puntos), + multiplicador: parseFloat(multiplicador), + periodoRenovacion, + renovacion: parseInt(renovacion, 10), + }; - // Validar los datos de entrada - const nuevoEvento = { - idCliente, - nombre, - descripcion, - puntos, - multiplicador, - periodoRenovacion, - renovacion, - }; + // Validación de datos + if (repositorio.crearEvento(nuevoEvento)) { + return res.status(201).json({ + codigo: MENSAJES_EVENTOS.EVENTO_CREADO.codigo, + mensaje: MENSAJES_EVENTOS.EVENTO_CREADO.mensaje, + }); + } - // Validación de datos - if (repositorio.crearEvento(nuevoEvento)) { - return res.status(201).json({ - codigo: MENSAJES_EVENTOS.EVENTO_CREADO.codigo, - mensaje: MENSAJES_EVENTOS.EVENTO_CREADO.mensaje, + } catch { + return res.status(MENSAJES_EVENTOS.ERROR_CREAR_EVENTO.codigo).json({ + mensaje: MENSAJES_EVENTOS.ERROR_CREAR_EVENTO.mensaje, }); } }; diff --git a/Eventos/Datos/Repositorios/repositorioCrearEvento.js b/Eventos/Datos/Repositorios/repositorioCrearEvento.js index 8e685629..b7f9694d 100644 --- a/Eventos/Datos/Repositorios/repositorioCrearEvento.js +++ b/Eventos/Datos/Repositorios/repositorioCrearEvento.js @@ -1,6 +1,6 @@ // RF36 - Crear Evento - [https://codeandco-wiki.netlify.app/docs/next/proyectos/textiles/documentacion/requisitos/RF36] -const db = require('@altertex/util/bd/db'); +const correrQuery = require('@altertex/util/ser/correrQuery'); const CONSULTAS_EVENTOS = require('@altertex/util/const/consultasEventos'); /** @@ -25,5 +25,24 @@ exports.crearEvento = async ({ periodoRenovacion, renovacion, }) => { - return true; + try { + + const query = CONSULTAS_EVENTOS.CREAR_EVENTO; + + const resultado = await correrQuery(query, [ + idCliente, + nombre, + descripcion, + puntos, + multiplicador, + periodoRenovacion, + renovacion, + ]); + + return resultado; + + } catch (error) { + console.error('Error al crear evento:', error); + throw error; + } }; From 8d3cf0c7741fc6b85c58e256d50a4adfa28812b7 Mon Sep 17 00:00:00 2001 From: DiegoGarciaPadilla Date: Fri, 23 May 2025 11:55:16 -0600 Subject: [PATCH 011/116] fix: mandar mensajes correctos --- .../Controladores/crearEvento.controller.js | 24 ++++++++------- .../Repositorios/repositorioCrearEvento.js | 29 +++++++------------ 2 files changed, 24 insertions(+), 29 deletions(-) diff --git a/Eventos/Controladores/crearEvento.controller.js b/Eventos/Controladores/crearEvento.controller.js index f4d37478..628260fd 100644 --- a/Eventos/Controladores/crearEvento.controller.js +++ b/Eventos/Controladores/crearEvento.controller.js @@ -20,10 +20,10 @@ const { parse } = require('dotenv'); * * @returns {object} - Respuesta JSON con el resultado de la operación. */ -exports.crearEvento = (req, res) => { +exports.crearEvento = async (req, res) => { try { - - const { idCliente, nombre, descripcion, puntos, multiplicador, periodoRenovacion, renovacion } = req.body; + const { idCliente, nombre, descripcion, puntos, multiplicador, periodoRenovacion, renovacion } = + req.body; // Validar los datos de entrada const nuevoEvento = { @@ -33,19 +33,21 @@ exports.crearEvento = (req, res) => { puntos: parseFloat(puntos), multiplicador: parseFloat(multiplicador), periodoRenovacion, - renovacion: parseInt(renovacion, 10), + renovacion: renovacion ? 1 : 0, }; - // Validación de datos - if (repositorio.crearEvento(nuevoEvento)) { - return res.status(201).json({ - codigo: MENSAJES_EVENTOS.EVENTO_CREADO.codigo, - mensaje: MENSAJES_EVENTOS.EVENTO_CREADO.mensaje, - }); - } + const resultado = await repositorio.crearEvento(nuevoEvento); + + // Verificar si el evento fue creado exitosamente + return res.status(MENSAJES_EVENTOS.EVENTO_CREADO.codigo).json({ + codigo: MENSAJES_EVENTOS.EVENTO_CREADO.codigo, + mensaje: MENSAJES_EVENTOS.EVENTO_CREADO.mensaje, + evento: resultado.evento, + }); } catch { return res.status(MENSAJES_EVENTOS.ERROR_CREAR_EVENTO.codigo).json({ + codigo: MENSAJES_EVENTOS.ERROR_CREAR_EVENTO.codigo, mensaje: MENSAJES_EVENTOS.ERROR_CREAR_EVENTO.mensaje, }); } diff --git a/Eventos/Datos/Repositorios/repositorioCrearEvento.js b/Eventos/Datos/Repositorios/repositorioCrearEvento.js index b7f9694d..a761e339 100644 --- a/Eventos/Datos/Repositorios/repositorioCrearEvento.js +++ b/Eventos/Datos/Repositorios/repositorioCrearEvento.js @@ -25,24 +25,17 @@ exports.crearEvento = async ({ periodoRenovacion, renovacion, }) => { - try { - - const query = CONSULTAS_EVENTOS.CREAR_EVENTO; - - const resultado = await correrQuery(query, [ - idCliente, - nombre, - descripcion, - puntos, - multiplicador, - periodoRenovacion, - renovacion, - ]); + const query = CONSULTAS_EVENTOS.CREAR_EVENTO; - return resultado; + const resultado = await correrQuery(query, [ + idCliente, + nombre, + descripcion, + puntos, + multiplicador, + periodoRenovacion, + renovacion, + ]); - } catch (error) { - console.error('Error al crear evento:', error); - throw error; - } + return resultado; }; From 289499bddbac332b680372c9d8509459d64e8e7f Mon Sep 17 00:00:00 2001 From: DiegoGarciaPadilla Date: Fri, 23 May 2025 12:11:12 -0600 Subject: [PATCH 012/116] fix: errores de linter --- Eventos/Controladores/crearEvento.controller.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Eventos/Controladores/crearEvento.controller.js b/Eventos/Controladores/crearEvento.controller.js index 628260fd..67e41ecb 100644 --- a/Eventos/Controladores/crearEvento.controller.js +++ b/Eventos/Controladores/crearEvento.controller.js @@ -2,7 +2,6 @@ const repositorio = require('@altertex/eve/repos/repositorioCrearEvento'); const MENSAJES_EVENTOS = require('@altertex/util/const/mensajesEventos'); -const { parse } = require('dotenv'); /** * Controlador para crear un nuevo evento. @@ -22,8 +21,7 @@ const { parse } = require('dotenv'); */ exports.crearEvento = async (req, res) => { try { - const { idCliente, nombre, descripcion, puntos, multiplicador, periodoRenovacion, renovacion } = - req.body; + const { idCliente, nombre, descripcion, puntos, multiplicador, periodoRenovacion, renovacion } = req.body; // Validar los datos de entrada const nuevoEvento = { From 6159f23adad31bc97b721b5f7727cba6d9859ddb Mon Sep 17 00:00:00 2001 From: max Date: Fri, 23 May 2025 16:27:48 -0600 Subject: [PATCH 013/116] cambio clienteRedis.js --- Configuracion/clienteRedis.js | 2 +- package-lock.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Configuracion/clienteRedis.js b/Configuracion/clienteRedis.js index 5f5f3a76..f6ec29e3 100644 --- a/Configuracion/clienteRedis.js +++ b/Configuracion/clienteRedis.js @@ -7,4 +7,4 @@ const redis = createClient({ redis.on('error', (err) => console.error('Error Redis', err)); redis.connect(); -module.exports = redis; \ No newline at end of file +module.exports = redis; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 0a1357e6..c9877cab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10422,6 +10422,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/redis/-/redis-5.1.0.tgz", "integrity": "sha512-5G5k9sYo5H5L0kd7UETiJZFTkIClH31fSmaEk2eU8E7mrmF0J1t6RqmFXOCJmSRlNd3QyvDUK/AWL9psbKaN0Q==", + "license": "MIT", "dependencies": { "@redis/bloom": "5.1.0", "@redis/client": "5.1.0", From 0e52ae20ba109b6535e06cb6ac1e08d5641393da Mon Sep 17 00:00:00 2001 From: DiegoGarciaPadilla Date: Fri, 23 May 2025 16:58:23 -0600 Subject: [PATCH 014/116] =?UTF-8?q?feat:=20agregar=20pruebas=20autom=C3=A1?= =?UTF-8?q?ticas=20con=20jest?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Utilidades/Constantes/mensajesEventos.js | 40 --- .../crearEvento.controller.test.js | 118 +++++++++ jest.config.js | 8 + package-lock.json | 234 +++++++++++++++++- package.json | 4 +- 5 files changed, 360 insertions(+), 44 deletions(-) create mode 100644 _tests_/Eventos/Controladores/crearEvento.controller.test.js diff --git a/Utilidades/Constantes/mensajesEventos.js b/Utilidades/Constantes/mensajesEventos.js index d6a3b265..d3faa462 100644 --- a/Utilidades/Constantes/mensajesEventos.js +++ b/Utilidades/Constantes/mensajesEventos.js @@ -6,14 +6,6 @@ module.exports = { }, // 200 - OK - CATEGORIA_OBTENIDA: { - codigo: 200, - mensaje: 'Información de la categoría obtenida exitosamente.', - }, - LISTA_CATEGORIAS_OBTENIDA: { - codigo: 200, - mensaje: 'Lista de categorías obtenida exitosamente.', - }, EVENTO_OBTENIDO: { codigo: 200, mensaje: 'Información del evento obtenida exitosamente.', @@ -54,10 +46,6 @@ module.exports = { codigo: 400, mensaje: 'Los parámetros proporcionados no son válidos.', }, - LIMITE_OFFSET_INVALIDOS: { - codigo: 400, - mensaje: 'Los valores de límite u offset deben ser números enteros positivos mayores a cero.', - }, NOMBRE_EVENTO_INVALIDO: { codigo: 400, mensaje: 'El nombre del evento proporcionado no es válido.', @@ -66,41 +54,13 @@ module.exports = { codigo: 400, mensaje: 'Ya existe un evento con ese nombre.', }, - - // 401 - No autorizado - CREDENCIALES_INVALIDAS: { - codigo: 401, - mensaje: 'Credenciales inválidas para consultar categorías.', - }, - - // 403 - Acceso denegado - ACCESO_DENEGADO: { - codigo: 403, - mensaje: 'No tiene permiso para realizar esta acción sobre categorías.', - }, // 404 - No encontrado - CATEGORIA_NO_ENCONTRADA: { - codigo: 404, - mensaje: 'No se encontró una categoría con el ID proporcionado.', - }, EVENTO_NO_ENCONTRADO: { codigo: 404, mensaje: 'No se encontró un evento con el ID proporcionado.', }, // 500 - Error del servidor - ERROR_CREAR_CATEGORIA: { - codigo: 500, - mensaje: 'Ocurrió un error al intentar crear la categoría.', - }, - ERROR_OBTENER_CATEGORIAS: { - codigo: 500, - mensaje: 'Ocurrió un error al obtener la lista de categorías.', - }, - ERROR_OBTENER_CATEGORIA: { - codigo: 500, - mensaje: 'Ocurrió un error al obtener los datos de la categoría.', - }, ERROR_CREAR_EVENTO: { codigo: 500, mensaje: 'Ocurrió un error al intentar crear el evento.', diff --git a/_tests_/Eventos/Controladores/crearEvento.controller.test.js b/_tests_/Eventos/Controladores/crearEvento.controller.test.js new file mode 100644 index 00000000..747f036c --- /dev/null +++ b/_tests_/Eventos/Controladores/crearEvento.controller.test.js @@ -0,0 +1,118 @@ +// Mock del repositorio +jest.mock('@altertex/eve/repos/repositorioCrearEvento', () => ({ + crearEvento: jest.fn(), +})); + +// Función a probar +const { crearEvento } = require('@altertex/eve/ctrl/crearEvento.controller'); +const repositorio = require('@altertex/eve/repos/repositorioCrearEvento'); // Mock del repositorio +const MENSAJES_EVENTOS = require('@altertex/util/const/mensajesEventos'); + +// Pruebas +describe('Controlador de Crear Evento', () => { + let req; + let res; + + beforeEach(() => { + // Reset de los mocks + jest.clearAllMocks(); + + // Mock de req y res + req = { + body: {}, + }; + + res = { + status: jest.fn().mockReturnThis(), + json: jest.fn(), + cookie: jest.fn(), + }; + }); + + test('Campos vacíos', async () => { + // Arrange + req.body = { + idCliente: '', + nombre: '', + descripcion: '', + puntos: '', + multiplicador: '', + periodoRenovacion: '', + renovacion: '', + }; + + // Act + await crearEvento(req, res); + + // Assert + expect(res.status).toHaveBeenCalledWith(MENSAJES_EVENTOS.ERROR_CREAR_EVENTO.codigo); + expect(res.json).toHaveBeenCalledWith({ + codigo: MENSAJES_EVENTOS.ERROR_CREAR_EVENTO.codigo, + mensaje: MENSAJES_EVENTOS.ERROR_CREAR_EVENTO.mensaje, + }); + }); + + test('Campos inválidos', async () => { + // Arrange + req.body = { + idCliente: 'abc', + nombre: 'Evento de prueba', + descripcion: 'Descripción del evento', + puntos: 'cien', + multiplicador: 'uno punto cinco', + periodoRenovacion: 'mensual', + renovacion: 'sí', + }; + + // Act + await crearEvento(req, res); + + // Assert + expect(res.status).toHaveBeenCalledWith(MENSAJES_EVENTOS.ERROR_CREAR_EVENTO.codigo); + expect(res.json).toHaveBeenCalledWith({ + codigo: MENSAJES_EVENTOS.ERROR_CREAR_EVENTO.codigo, + mensaje: MENSAJES_EVENTOS.ERROR_CREAR_EVENTO.mensaje, + }); + }); + + test('Evento creado exitosamente', async () => { + // Arrange + req.body = { + idCliente: '1', + nombre: 'Evento de prueba', + descripcion: 'Descripción del evento', + puntos: '100', + multiplicador: '1.5', + periodoRenovacion: 'mensual', + renovacion: true, + }; + + const eventoCreado = { + id: 1, + nombre: 'Evento de prueba' + }; + + repositorio.crearEvento.mockResolvedValue({ evento: eventoCreado }); + + // Act + await crearEvento(req, res); + + // Assert + expect(repositorio.crearEvento).toHaveBeenCalledWith({ + idCliente: 1, + nombre: 'Evento de prueba', + descripcion: 'Descripción del evento', + puntos: 100, + multiplicador: 1.5, + periodoRenovacion: 'mensual', + renovacion: 1, + }); + + expect(res.status).toHaveBeenCalledWith(MENSAJES_EVENTOS.EVENTO_CREADO.codigo); + expect(res.json).toHaveBeenCalledWith({ + codigo: MENSAJES_EVENTOS.EVENTO_CREADO.codigo, + mensaje: MENSAJES_EVENTOS.EVENTO_CREADO.mensaje, + evento: eventoCreado, + }); + }); +}); diff --git a/jest.config.js b/jest.config.js index ae7116a4..6466bef9 100644 --- a/jest.config.js +++ b/jest.config.js @@ -60,6 +60,14 @@ module.exports = { "^@altertex/emp/datos/(.*)$": "/Empleados/Datos/$1", "^@altertex/emp/(.*)$": "/Empleados/$1", + // Eventos module mappings + "^@altertex/eve/ctrl/(.*)$": "/Eventos/Controladores/$1", + "^@altertex/eve/repos/(.*)$": "/Eventos/Datos/Repositorios/$1", + "^@altertex/eve/rutasInd/(.*)$": "/Eventos/Rutas/RutasIndividuales/$1", + "^@altertex/eve/rutas/(.*)$": "/Eventos/Rutas/$1", + "^@altertex/eve/datos/(.*)$": "/Eventos/Datos/$1", + "^@altertex/eve/(.*)$": "/Eventos/$1", + // Generic mapping as fallback '^@altertex/(.*)$': '/$1', }, diff --git a/package-lock.json b/package-lock.json index 23c6c96a..2b46b65b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,6 @@ "dotenv": "^16.4.7", "express": "^4.21.2", "helmet": "^8.1.0", - "jest": "^29.7.0", "jsonwebtoken": "^9.0.2", "module-alias": "^2.2.3", "multer": "^1.4.5-lts.1", @@ -42,13 +41,15 @@ "devDependencies": { "@eslint/js": "^9.23.0", "eslint": "^9.22.0", - "eslint-plugin-jsdoc": "^50.6.11" + "eslint-plugin-jsdoc": "^50.6.11", + "jest": "^29.7.0" } }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -1149,6 +1150,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", @@ -1163,6 +1165,7 @@ "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.2.tgz", "integrity": "sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1172,6 +1175,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.1.tgz", "integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==", + "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", @@ -1202,6 +1206,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -1219,12 +1224,14 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, "license": "MIT" }, "node_modules/@babel/core/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -1234,6 +1241,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.1.tgz", "integrity": "sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==", + "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.27.1", @@ -1250,6 +1258,7 @@ "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/compat-data": "^7.27.2", @@ -1266,6 +1275,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -1275,6 +1285,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.27.1", @@ -1288,6 +1299,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.1.tgz", "integrity": "sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.27.1", @@ -1305,6 +1317,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1314,6 +1327,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1323,6 +1337,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1332,6 +1347,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1341,6 +1357,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.1.tgz", "integrity": "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.27.1", @@ -1354,6 +1371,7 @@ "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz", "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.27.1" @@ -1369,6 +1387,7 @@ "version": "7.8.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" @@ -1381,6 +1400,7 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" @@ -1393,6 +1413,7 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.12.13" @@ -1405,6 +1426,7 @@ "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" @@ -1420,6 +1442,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -1435,6 +1458,7 @@ "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" @@ -1447,6 +1471,7 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" @@ -1459,6 +1484,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -1474,6 +1500,7 @@ "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" @@ -1486,6 +1513,7 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" @@ -1498,6 +1526,7 @@ "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" @@ -1510,6 +1539,7 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" @@ -1522,6 +1552,7 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" @@ -1534,6 +1565,7 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" @@ -1546,6 +1578,7 @@ "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" @@ -1561,6 +1594,7 @@ "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" @@ -1576,6 +1610,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -1612,6 +1647,7 @@ "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", @@ -1626,6 +1662,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.1.tgz", "integrity": "sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", @@ -1644,6 +1681,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -1661,6 +1699,7 @@ "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -1670,12 +1709,14 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, "license": "MIT" }, "node_modules/@babel/types": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -1689,6 +1730,7 @@ "version": "0.2.3", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, "license": "MIT" }, "node_modules/@es-joy/jsdoccomment": { @@ -1964,6 +2006,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, "license": "ISC", "dependencies": { "camelcase": "^5.3.1", @@ -1980,6 +2023,7 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, "license": "MIT", "dependencies": { "sprintf-js": "~1.0.2" @@ -1989,6 +2033,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, "license": "MIT", "dependencies": { "locate-path": "^5.0.0", @@ -2002,6 +2047,7 @@ "version": "3.14.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, "license": "MIT", "dependencies": { "argparse": "^1.0.7", @@ -2015,6 +2061,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, "license": "MIT", "dependencies": { "p-locate": "^4.1.0" @@ -2027,6 +2074,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, "license": "MIT", "dependencies": { "p-try": "^2.0.0" @@ -2042,6 +2090,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, "license": "MIT", "dependencies": { "p-limit": "^2.2.0" @@ -2054,6 +2103,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -2063,12 +2113,14 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, "license": "BSD-3-Clause" }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -2078,6 +2130,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", @@ -2095,6 +2148,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, "license": "MIT", "dependencies": { "@jest/console": "^29.7.0", @@ -2142,6 +2196,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, "license": "MIT", "dependencies": { "@jest/fake-timers": "^29.7.0", @@ -2157,6 +2212,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, "license": "MIT", "dependencies": { "expect": "^29.7.0", @@ -2170,6 +2226,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, "license": "MIT", "dependencies": { "jest-get-type": "^29.6.3" @@ -2182,6 +2239,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", @@ -2199,6 +2257,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, "license": "MIT", "dependencies": { "@jest/environment": "^29.7.0", @@ -2214,6 +2273,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^0.2.3", @@ -2257,6 +2317,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, "license": "MIT", "dependencies": { "@sinclair/typebox": "^0.27.8" @@ -2269,6 +2330,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.18", @@ -2283,6 +2345,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, "license": "MIT", "dependencies": { "@jest/console": "^29.7.0", @@ -2298,6 +2361,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, "license": "MIT", "dependencies": { "@jest/test-result": "^29.7.0", @@ -2313,6 +2377,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.11.6", @@ -2339,6 +2404,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, "license": "MIT", "dependencies": { "@jest/schemas": "^29.6.3", @@ -2356,6 +2422,7 @@ "version": "0.3.8", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/set-array": "^1.2.1", @@ -2370,6 +2437,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -2379,6 +2447,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -2388,12 +2457,14 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -3102,12 +3173,14 @@ "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, "license": "MIT" }, "node_modules/@sinonjs/commons": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "type-detect": "4.0.8" @@ -3117,6 +3190,7 @@ "version": "10.3.0", "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "@sinonjs/commons": "^3.0.0" @@ -4442,6 +4516,7 @@ "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.20.7", @@ -4455,6 +4530,7 @@ "version": "7.27.0", "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.0.0" @@ -4464,6 +4540,7 @@ "version": "7.4.4", "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.1.0", @@ -4474,6 +4551,7 @@ "version": "7.20.7", "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", + "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.20.7" @@ -4490,6 +4568,7 @@ "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -4508,12 +4587,14 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, "license": "MIT" }, "node_modules/@types/istanbul-lib-report": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, "license": "MIT", "dependencies": { "@types/istanbul-lib-coverage": "*" @@ -4523,6 +4604,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, "license": "MIT", "dependencies": { "@types/istanbul-lib-report": "*" @@ -4538,6 +4620,7 @@ "version": "22.15.17", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.17.tgz", "integrity": "sha512-wIX2aSZL5FE+MR0JlvF87BNVrtFWf6AE6rxSE9X7OwnVvoyCQjpzSRJ+M87se/4QCkCiebQAqrJ0y6fwIyi7nw==", + "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -4556,6 +4639,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, "license": "MIT" }, "node_modules/@types/trusted-types": { @@ -4587,6 +4671,7 @@ "version": "17.0.33", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, "license": "MIT", "dependencies": { "@types/yargs-parser": "*" @@ -4596,6 +4681,7 @@ "version": "21.0.3", "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, "license": "MIT" }, "node_modules/accepts": { @@ -4688,6 +4774,7 @@ "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, "license": "MIT", "dependencies": { "type-fest": "^0.21.3" @@ -4879,6 +4966,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, "license": "MIT", "dependencies": { "@jest/transform": "^29.7.0", @@ -4900,6 +4988,7 @@ "version": "6.1.1", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", @@ -4916,6 +5005,7 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "@babel/core": "^7.12.3", @@ -4932,6 +5022,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -4941,6 +5032,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.3.3", @@ -4956,6 +5048,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", @@ -4982,6 +5075,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, "license": "MIT", "dependencies": { "babel-plugin-jest-hoist": "^29.6.3", @@ -5130,6 +5224,7 @@ "version": "4.24.5", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==", + "dev": true, "funding": [ { "type": "opencollective", @@ -5162,6 +5257,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "node-int64": "^0.4.0" @@ -5267,6 +5363,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -5285,6 +5382,7 @@ "version": "1.0.30001718", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz", "integrity": "sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==", + "dev": true, "funding": [ { "type": "opencollective", @@ -5305,6 +5403,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -5321,6 +5420,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -5402,6 +5502,7 @@ "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, "funding": [ { "type": "github", @@ -5417,6 +5518,7 @@ "version": "1.4.3", "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, "license": "MIT" }, "node_modules/classnames": { @@ -5453,6 +5555,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -5475,6 +5578,7 @@ "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, "license": "MIT", "engines": { "iojs": ">= 1.0.0", @@ -5485,6 +5589,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true, "license": "MIT" }, "node_modules/color-convert": { @@ -5598,6 +5703,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, "license": "MIT" }, "node_modules/cookie": { @@ -5677,6 +5783,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", @@ -5704,6 +5811,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -5763,6 +5871,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==", + "dev": true, "license": "MIT", "peerDependencies": { "babel-plugin-macros": "^3.1.0" @@ -5870,6 +5979,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -5889,6 +5999,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, "license": "MIT", "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -5975,12 +6086,14 @@ "version": "1.5.152", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.152.tgz", "integrity": "sha512-xBOfg/EBaIlVsHipHl2VdTPJRSvErNUaqW8ejTq5OlOlIYx1wOllCHsAvAIrr55jD1IYEfdR86miUEt8H5IeJg==", + "dev": true, "license": "ISC" }, "node_modules/emittery": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -6020,6 +6133,7 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" @@ -6074,6 +6188,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -6412,6 +6527,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, "license": "MIT", "dependencies": { "cross-spawn": "^7.0.3", @@ -6435,6 +6551,7 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, "engines": { "node": ">= 0.8.0" } @@ -6443,6 +6560,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, "license": "MIT", "dependencies": { "@jest/expect-utils": "^29.7.0", @@ -6552,6 +6670,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, "license": "MIT" }, "node_modules/fast-levenshtein": { @@ -6606,6 +6725,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "bser": "2.1.1" @@ -6842,6 +6962,7 @@ "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -6884,6 +7005,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=8.0.0" @@ -6906,6 +7028,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -6968,6 +7091,7 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -7026,6 +7150,7 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, "license": "ISC" }, "node_modules/has-flag": { @@ -7149,6 +7274,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, "license": "MIT" }, "node_modules/http-errors": { @@ -7243,6 +7369,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=10.17.0" @@ -7312,6 +7439,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, "license": "MIT", "dependencies": { "pkg-dir": "^4.2.0", @@ -7331,6 +7459,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.8.19" @@ -7440,6 +7569,7 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, "license": "MIT" }, "node_modules/is-binary-path": { @@ -7513,6 +7643,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -7602,6 +7733,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -7635,12 +7767,14 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, "license": "ISC" }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=8" @@ -7650,6 +7784,7 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "@babel/core": "^7.23.9", @@ -7666,6 +7801,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "istanbul-lib-coverage": "^3.0.0", @@ -7680,6 +7816,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "debug": "^4.1.1", @@ -7694,6 +7831,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -7711,12 +7849,14 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, "license": "MIT" }, "node_modules/istanbul-reports": { "version": "3.1.7", "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "html-escaper": "^2.0.0", @@ -7730,6 +7870,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, "license": "MIT", "dependencies": { "@jest/core": "^29.7.0", @@ -7756,6 +7897,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, "license": "MIT", "dependencies": { "execa": "^5.0.0", @@ -7770,6 +7912,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, "license": "MIT", "dependencies": { "@jest/environment": "^29.7.0", @@ -7801,6 +7944,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, "license": "MIT", "dependencies": { "@jest/core": "^29.7.0", @@ -7834,6 +7978,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.11.6", @@ -7879,6 +8024,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.0.0", @@ -7894,6 +8040,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, "license": "MIT", "dependencies": { "detect-newline": "^3.0.0" @@ -7906,6 +8053,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", @@ -7922,6 +8070,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, "license": "MIT", "dependencies": { "@jest/environment": "^29.7.0", @@ -7939,6 +8088,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, "license": "MIT", "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -7948,6 +8098,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", @@ -7973,6 +8124,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, "license": "MIT", "dependencies": { "jest-get-type": "^29.6.3", @@ -7986,6 +8138,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.0.0", @@ -8001,6 +8154,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.12.13", @@ -8021,6 +8175,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", @@ -8035,6 +8190,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -8052,6 +8208,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, "license": "MIT", "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -8061,6 +8218,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.0.0", @@ -8081,6 +8239,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, "license": "MIT", "dependencies": { "jest-regex-util": "^29.6.3", @@ -8094,6 +8253,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, "license": "MIT", "dependencies": { "@jest/console": "^29.7.0", @@ -8126,6 +8286,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, "license": "MIT", "dependencies": { "@jest/environment": "^29.7.0", @@ -8159,6 +8320,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.11.6", @@ -8190,6 +8352,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", @@ -8207,6 +8370,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", @@ -8224,6 +8388,7 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -8236,6 +8401,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, "license": "MIT", "dependencies": { "@jest/test-result": "^29.7.0", @@ -8255,6 +8421,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", @@ -8270,6 +8437,7 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -8346,6 +8514,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, "license": "MIT", "bin": { "jsesc": "bin/jsesc" @@ -8365,6 +8534,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, "license": "MIT" }, "node_modules/json-schema-traverse": { @@ -8392,6 +8562,7 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, "license": "MIT", "bin": { "json5": "lib/cli.js" @@ -8463,6 +8634,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -8481,6 +8653,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -8504,6 +8677,7 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, "license": "MIT" }, "node_modules/locate-path": { @@ -8639,6 +8813,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, "license": "ISC", "dependencies": { "yallist": "^3.0.2" @@ -8663,6 +8838,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, "license": "MIT", "dependencies": { "semver": "^7.5.3" @@ -8678,6 +8854,7 @@ "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "tmpl": "1.0.5" @@ -8714,6 +8891,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, "license": "MIT" }, "node_modules/methods": { @@ -8729,6 +8907,7 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -8775,6 +8954,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -8951,6 +9131,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, "license": "MIT" }, "node_modules/needle": { @@ -9108,12 +9289,14 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, "license": "MIT" }, "node_modules/node-releases": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, "license": "MIT" }, "node_modules/nodemon": { @@ -9201,6 +9384,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.0.0" @@ -9280,6 +9464,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, "license": "MIT", "dependencies": { "mimic-fn": "^2.1.0" @@ -9344,6 +9529,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" @@ -9486,6 +9672,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.0.0", @@ -9538,6 +9725,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -9559,6 +9747,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, "license": "ISC" }, "node_modules/picomatch": { @@ -9589,6 +9778,7 @@ "version": "4.0.7", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -9608,6 +9798,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, "license": "MIT", "dependencies": { "find-up": "^4.0.0" @@ -9620,6 +9811,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, "license": "MIT", "dependencies": { "locate-path": "^5.0.0", @@ -9633,6 +9825,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, "license": "MIT", "dependencies": { "p-locate": "^4.1.0" @@ -9645,6 +9838,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, "license": "MIT", "dependencies": { "p-try": "^2.0.0" @@ -9660,6 +9854,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, "license": "MIT", "dependencies": { "p-limit": "^2.2.0" @@ -9954,6 +10149,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, "license": "MIT", "dependencies": { "@jest/schemas": "^29.6.3", @@ -9968,6 +10164,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -10013,6 +10210,7 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, "license": "MIT", "dependencies": { "kleur": "^3.0.3", @@ -10142,6 +10340,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, "funding": [ { "type": "individual", @@ -10498,6 +10697,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, "license": "MIT" }, "node_modules/react-redux": { @@ -10767,6 +10967,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, "license": "MIT", "dependencies": { "resolve-from": "^5.0.0" @@ -10779,6 +10980,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -10798,6 +11000,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -11109,6 +11312,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -11121,6 +11325,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -11236,12 +11441,14 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, "license": "MIT" }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -11321,6 +11528,7 @@ "version": "0.5.13", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", @@ -11393,6 +11601,7 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, "license": "MIT", "dependencies": { "escape-string-regexp": "^2.0.0" @@ -11405,6 +11614,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -11470,6 +11680,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, "license": "MIT", "dependencies": { "char-regex": "^1.0.2", @@ -11509,6 +11720,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -11518,6 +11730,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -11527,6 +11740,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -11845,6 +12059,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, "license": "ISC", "dependencies": { "@istanbuljs/schema": "^0.1.2", @@ -11859,6 +12074,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, "license": "BSD-3-Clause" }, "node_modules/to-regex-range": { @@ -11993,6 +12209,7 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -12002,6 +12219,7 @@ "version": "0.21.3", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" @@ -12048,6 +12266,7 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, "license": "MIT" }, "node_modules/unpipe": { @@ -12069,6 +12288,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, "funding": [ { "type": "opencollective", @@ -12185,6 +12405,7 @@ "version": "9.3.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, "license": "ISC", "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", @@ -12241,6 +12462,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "makeerror": "1.0.12" @@ -12266,6 +12488,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -12318,6 +12541,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -12341,6 +12565,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4", @@ -12421,6 +12646,7 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, "license": "ISC", "engines": { "node": ">=10" @@ -12430,6 +12656,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, "license": "ISC" }, "node_modules/yaml": { @@ -12445,6 +12672,7 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -12463,6 +12691,7 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -12472,6 +12701,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" diff --git a/package.json b/package.json index a503511e..586c0734 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,6 @@ "dotenv": "^16.4.7", "express": "^4.21.2", "helmet": "^8.1.0", - "jest": "^29.7.0", "jsonwebtoken": "^9.0.2", "module-alias": "^2.2.3", "multer": "^1.4.5-lts.1", @@ -46,7 +45,8 @@ "devDependencies": { "@eslint/js": "^9.23.0", "eslint": "^9.22.0", - "eslint-plugin-jsdoc": "^50.6.11" + "eslint-plugin-jsdoc": "^50.6.11", + "jest": "^29.7.0" }, "_moduleAliases": { "@altertex/root": ".", From 80c6e2a48953b0dc8a0643bbfa16ffa6b74f9b54 Mon Sep 17 00:00:00 2001 From: DiegoGarciaPadilla Date: Sun, 25 May 2025 15:57:32 -0600 Subject: [PATCH 015/116] fix: usar middleware para limitar peticiones --- Eventos/Rutas/RutasIndividuales/crearEvento.routes.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Eventos/Rutas/RutasIndividuales/crearEvento.routes.js b/Eventos/Rutas/RutasIndividuales/crearEvento.routes.js index cd47f9b4..97feadf0 100644 --- a/Eventos/Rutas/RutasIndividuales/crearEvento.routes.js +++ b/Eventos/Rutas/RutasIndividuales/crearEvento.routes.js @@ -4,6 +4,7 @@ const ruteador = express.Router(); const controlador = require('@altertex/eve/ctrl/crearEvento.controller'); const revisarApiKey = require('@altertex/util/inter/revisarApiKey'); const autorizarToken = require('@altertex/util/inter/autorizarToken'); +const limitePeticionesDiarias = require('@altertex/util/inter/limitePeticiones'); const validarYSanitizar = require('@altertex/util/inter/validarYSanitizar'); const verificarPermisos = require('@altertex/util/inter/verificarPermisos'); @@ -76,6 +77,7 @@ ruteador.post( validarYSanitizar, revisarApiKey(), autorizarToken, + limitePeticionesDiarias, verificarPermisos(PERMISOS.CREAR_EVENTO), controlador.crearEvento ); From a6f23a64f9376ec5198fe36ce62028f1fe45a5da Mon Sep 17 00:00:00 2001 From: DiegoGarciaPadilla Date: Sun, 25 May 2025 16:39:33 -0600 Subject: [PATCH 016/116] =?UTF-8?q?feat:=20manejo=20de=20errores=20m=C3=A1?= =?UTF-8?q?s=20s=C3=B3lido?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controladores/crearEvento.controller.js | 38 +++++++-- .../Repositorios/repositorioCrearEvento.js | 47 +++++++--- Utilidades/Constantes/mensajesEventos.js | 14 +++ .../crearEvento.controller.test.js | 85 +++++++++++++++++-- 4 files changed, 155 insertions(+), 29 deletions(-) diff --git a/Eventos/Controladores/crearEvento.controller.js b/Eventos/Controladores/crearEvento.controller.js index 67e41ecb..fa769be6 100644 --- a/Eventos/Controladores/crearEvento.controller.js +++ b/Eventos/Controladores/crearEvento.controller.js @@ -23,14 +23,35 @@ exports.crearEvento = async (req, res) => { try { const { idCliente, nombre, descripcion, puntos, multiplicador, periodoRenovacion, renovacion } = req.body; + // Validaciones básicas de campos requeridos (descripcion y periodoRenovacion son opcionales) + if (!idCliente || !nombre || !puntos || !multiplicador) { + + return res.status(MENSAJES_EVENTOS.PARAMETROS_INVALIDOS.codigo).json({ + codigo: MENSAJES_EVENTOS.PARAMETROS_INVALIDOS.codigo, + mensaje: MENSAJES_EVENTOS.PARAMETROS_INVALIDOS.mensaje, + }); + } + // Validar los datos de entrada + const idClienteNum = parseInt(idCliente, 10); + const puntosNum = parseFloat(puntos); + const multiplicadorNum = parseFloat(multiplicador); + + // Validaciones de formato + if (isNaN(idClienteNum) || idClienteNum <= 0) { + return res.status(MENSAJES_EVENTOS.PARAMETROS_INVALIDOS.codigo).json({ + codigo: MENSAJES_EVENTOS.PARAMETROS_INVALIDOS.codigo, + mensaje: 'El ID del cliente debe ser un número válido mayor a 0.', + }); + } + const nuevoEvento = { - idCliente: parseInt(idCliente, 10), + idCliente: idClienteNum, nombre, - descripcion, - puntos: parseFloat(puntos), - multiplicador: parseFloat(multiplicador), - periodoRenovacion, + descripcion: descripcion && descripcion.trim() !== '' ? descripcion : null, + puntos: puntosNum, + multiplicador: multiplicadorNum, + periodoRenovacion: periodoRenovacion && periodoRenovacion.trim() !== '' ? periodoRenovacion : null, renovacion: renovacion ? 1 : 0, }; @@ -40,13 +61,14 @@ exports.crearEvento = async (req, res) => { return res.status(MENSAJES_EVENTOS.EVENTO_CREADO.codigo).json({ codigo: MENSAJES_EVENTOS.EVENTO_CREADO.codigo, mensaje: MENSAJES_EVENTOS.EVENTO_CREADO.mensaje, - evento: resultado.evento, + evento: resultado?.evento || resultado, }); - } catch { + } catch (error) { + // Usar el mensaje personalizado del error en la respuesta return res.status(MENSAJES_EVENTOS.ERROR_CREAR_EVENTO.codigo).json({ codigo: MENSAJES_EVENTOS.ERROR_CREAR_EVENTO.codigo, - mensaje: MENSAJES_EVENTOS.ERROR_CREAR_EVENTO.mensaje, + mensaje: error.message || MENSAJES_EVENTOS.ERROR_CREAR_EVENTO.mensaje, }); } }; diff --git a/Eventos/Datos/Repositorios/repositorioCrearEvento.js b/Eventos/Datos/Repositorios/repositorioCrearEvento.js index a761e339..55d09b47 100644 --- a/Eventos/Datos/Repositorios/repositorioCrearEvento.js +++ b/Eventos/Datos/Repositorios/repositorioCrearEvento.js @@ -2,6 +2,7 @@ const correrQuery = require('@altertex/util/ser/correrQuery'); const CONSULTAS_EVENTOS = require('@altertex/util/const/consultasEventos'); +const MENSAJES_EVENTOS = require('@altertex/util/const/mensajesEventos'); /** * Crea un nuevo evento en la base de datos @@ -15,6 +16,7 @@ const CONSULTAS_EVENTOS = require('@altertex/util/const/consultasEventos'); * @param {number} evento.periodoRenovacion - Periodo de renovación del evento * @param {number} evento.renovacion - Renovación del evento * @returns {object} - Resultado de la operación de creación + * @throws {Error} - Error personalizado según el problema encontrado */ exports.crearEvento = async ({ idCliente, @@ -25,17 +27,38 @@ exports.crearEvento = async ({ periodoRenovacion, renovacion, }) => { - const query = CONSULTAS_EVENTOS.CREAR_EVENTO; + try { + const query = CONSULTAS_EVENTOS.CREAR_EVENTO; - const resultado = await correrQuery(query, [ - idCliente, - nombre, - descripcion, - puntos, - multiplicador, - periodoRenovacion, - renovacion, - ]); - - return resultado; + const resultado = await correrQuery(query, [ + idCliente, + nombre, + descripcion, + puntos, + multiplicador, + periodoRenovacion, + renovacion, + ]); + + // Si no hay resultado + if (!resultado) { + throw new Error(MENSAJES_EVENTOS.ERROR_INESPERADO.mensaje); + } + + return resultado; + } catch (error) { + // Si es un error que ya hemos generado, lo lanzamos tal cual + const mensajesError = Object.values(MENSAJES_EVENTOS).map(msg => msg.mensaje); + if (mensajesError.includes(error.message)) { + throw error; + } + + // Interpretamos posibles errores SQL - solo nos interesa si el cliente no existe + if (error.message.includes('foreign key constraint') || error.message.includes('FOREIGN KEY')) { + throw new Error(MENSAJES_EVENTOS.ERROR_CLIENTE_NO_EXISTE.mensaje); + } + + // Si es otro tipo de error, lanzamos un error genérico + throw new Error(`${MENSAJES_EVENTOS.ERROR_INESPERADO.mensaje}: ${error.message}`); + } }; diff --git a/Utilidades/Constantes/mensajesEventos.js b/Utilidades/Constantes/mensajesEventos.js index d3faa462..78590e3b 100644 --- a/Utilidades/Constantes/mensajesEventos.js +++ b/Utilidades/Constantes/mensajesEventos.js @@ -54,6 +54,12 @@ module.exports = { codigo: 400, mensaje: 'Ya existe un evento con ese nombre.', }, + // 400 - Error de cliente + ERROR_CLIENTE_NO_EXISTE: { + codigo: 400, + mensaje: 'El cliente especificado no existe en el sistema.', + }, + // 404 - No encontrado EVENTO_NO_ENCONTRADO: { codigo: 404, @@ -81,4 +87,12 @@ module.exports = { codigo: 500, mensaje: 'Ocurrió un error interno en el servidor.', }, + ERROR_DB_CONEXION: { + codigo: 500, + mensaje: 'Error de conexión con la base de datos.', + }, + ERROR_INESPERADO: { + codigo: 500, + mensaje: 'Ocurrió un error inesperado al crear el evento.', + }, }; diff --git a/_tests_/Eventos/Controladores/crearEvento.controller.test.js b/_tests_/Eventos/Controladores/crearEvento.controller.test.js index 747f036c..68c0613d 100644 --- a/_tests_/Eventos/Controladores/crearEvento.controller.test.js +++ b/_tests_/Eventos/Controladores/crearEvento.controller.test.js @@ -29,26 +29,67 @@ describe('Controlador de Crear Evento', () => { }; }); - test('Campos vacíos', async () => { - // Arrange + test('Campos requeridos vacíos', async () => { + // Arrange - Solo faltan los campos requeridos req.body = { idCliente: '', nombre: '', - descripcion: '', + descripcion: '', // Opcional - está bien que esté vacío puntos: '', multiplicador: '', - periodoRenovacion: '', - renovacion: '', + periodoRenovacion: '', // Opcional - está bien que esté vacío + renovacion: false, }; // Act await crearEvento(req, res); // Assert - expect(res.status).toHaveBeenCalledWith(MENSAJES_EVENTOS.ERROR_CREAR_EVENTO.codigo); + expect(res.status).toHaveBeenCalledWith(MENSAJES_EVENTOS.PARAMETROS_INVALIDOS.codigo); expect(res.json).toHaveBeenCalledWith({ - codigo: MENSAJES_EVENTOS.ERROR_CREAR_EVENTO.codigo, - mensaje: MENSAJES_EVENTOS.ERROR_CREAR_EVENTO.mensaje, + codigo: MENSAJES_EVENTOS.PARAMETROS_INVALIDOS.codigo, + mensaje: MENSAJES_EVENTOS.PARAMETROS_INVALIDOS.mensaje, + }); + }); + + test('Evento con campos opcionales vacíos', async () => { + // Arrange - Campos requeridos llenos, opcionales vacíos + req.body = { + idCliente: '1', + nombre: 'Evento de prueba', + descripcion: '', // Campo opcional vacío + puntos: '100', + multiplicador: '1.5', + periodoRenovacion: '', // Campo opcional vacío + renovacion: true, + }; + + const eventoCreado = { + id: 1, + nombre: 'Evento de prueba' + }; + + repositorio.crearEvento.mockResolvedValue({ evento: eventoCreado }); + + // Act + await crearEvento(req, res); + + // Assert + expect(repositorio.crearEvento).toHaveBeenCalledWith({ + idCliente: 1, + nombre: 'Evento de prueba', + descripcion: null, // Se convierte a null cuando está vacío + puntos: 100, + multiplicador: 1.5, + periodoRenovacion: null, // Se convierte a null cuando está vacío + renovacion: 1, + }); + + expect(res.status).toHaveBeenCalledWith(MENSAJES_EVENTOS.EVENTO_CREADO.codigo); + expect(res.json).toHaveBeenCalledWith({ + codigo: MENSAJES_EVENTOS.EVENTO_CREADO.codigo, + mensaje: MENSAJES_EVENTOS.EVENTO_CREADO.mensaje, + evento: eventoCreado, }); }); @@ -67,11 +108,37 @@ describe('Controlador de Crear Evento', () => { // Act await crearEvento(req, res); + // Assert + expect(res.status).toHaveBeenCalledWith(MENSAJES_EVENTOS.PARAMETROS_INVALIDOS.codigo); + expect(res.json).toHaveBeenCalledWith({ + codigo: MENSAJES_EVENTOS.PARAMETROS_INVALIDOS.codigo, + mensaje: 'El ID del cliente debe ser un número válido mayor a 0.', + }); + }); + + test('Cliente inexistente (error del repositorio)', async () => { + // Arrange + req.body = { + idCliente: '999', + nombre: 'Evento de prueba', + descripcion: 'Descripción del evento', + puntos: '100', + multiplicador: '1.5', + periodoRenovacion: 'mensual', + renovacion: true, + }; + + // Simulamos un error específico del repositorio + repositorio.crearEvento.mockRejectedValue(new Error(MENSAJES_EVENTOS.ERROR_CLIENTE_NO_EXISTE.mensaje)); + + // Act + await crearEvento(req, res); + // Assert expect(res.status).toHaveBeenCalledWith(MENSAJES_EVENTOS.ERROR_CREAR_EVENTO.codigo); expect(res.json).toHaveBeenCalledWith({ codigo: MENSAJES_EVENTOS.ERROR_CREAR_EVENTO.codigo, - mensaje: MENSAJES_EVENTOS.ERROR_CREAR_EVENTO.mensaje, + mensaje: MENSAJES_EVENTOS.ERROR_CLIENTE_NO_EXISTE.mensaje, }); }); From 97cc8046f766e5523a76bcc201e152dec8103d20 Mon Sep 17 00:00:00 2001 From: Diego Alfaro Pinto Date: Mon, 26 May 2025 01:04:03 -0600 Subject: [PATCH 017/116] feat: cambiar la consulta para que me de el nombre de la empresa del proveedor, y usar un query param para que no sea un post --- Productos/Controladores/leerProducto.controller.js | 5 ++--- Utilidades/Constantes/consultasProductos.js | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Productos/Controladores/leerProducto.controller.js b/Productos/Controladores/leerProducto.controller.js index 888337d0..5303e6f6 100644 --- a/Productos/Controladores/leerProducto.controller.js +++ b/Productos/Controladores/leerProducto.controller.js @@ -20,17 +20,16 @@ const repositorio = require('@altertex/pro/repos/repositorioLeerProducto'); * @throws {Error} Lanza un error si `idProducto` no es válido o si ocurre un error al consultar el repositorio. */ exports.leerProducto = async (req, res) => { - const idProducto = req.body.idProducto; + const idProducto = req.query.idProducto; const idCliente = req.user.clienteSeleccionado; if (!idProducto) { - throw new Error(MENSAJES.ID_INVALIDO.mensaje); + return res.status(MENSAJES.ID_INVALIDO.codigo).json({ mensaje: MENSAJES.ID_INVALIDO.mensaje }); } try { const infoProducto = await repositorio.leerProducto(idProducto, idCliente); return res.status(MENSAJES.LEER_PRODUCTO_EXITO.codigo).json({ - mensaje: MENSAJES.LEER_PRODUCTO_EXITO.mensaje, infoProducto, }); } catch (error) { diff --git a/Utilidades/Constantes/consultasProductos.js b/Utilidades/Constantes/consultasProductos.js index abbdca58..0f133d39 100644 --- a/Utilidades/Constantes/consultasProductos.js +++ b/Utilidades/Constantes/consultasProductos.js @@ -42,7 +42,7 @@ module.exports = { 'descuento', p.descuento, 'estado', p.estado, 'envio', p.envio, - 'nombreProveedor', pr.nombre, + 'nombreProveedor', pr.nombreCompania, 'variantes', (SELECT JSON_ARRAYAGG( JSON_OBJECT( 'idVariante', v.idVariante, From f473ef58f35e1df8487684485a22b4b2cc3979de Mon Sep 17 00:00:00 2001 From: NicoH00d Date: Mon, 26 May 2025 15:03:17 -0600 Subject: [PATCH 018/116] feature: recibir Productos desde el frontend --- .../importarProductos.controller.js | 84 +++++++++++++++++++ .../importarProductos.routes.js | 27 ++++++ Productos/Rutas/indexProductos.routes.js | 3 + Utilidades/Constantes/rutas.js | 1 + 4 files changed, 115 insertions(+) create mode 100644 Productos/Controladores/importarProductos.controller.js create mode 100644 Productos/Rutas/RutasIndividuales/importarProductos.routes.js diff --git a/Productos/Controladores/importarProductos.controller.js b/Productos/Controladores/importarProductos.controller.js new file mode 100644 index 00000000..de672841 --- /dev/null +++ b/Productos/Controladores/importarProductos.controller.js @@ -0,0 +1,84 @@ +const validarProducto = require('@altertex/util/vali/validarProducto'); +const validarVariante = require('@altertex/util/vali/validarVariante'); +const validarOpciones = require('@altertex/util/vali/validarOpciones'); +const repositorioCrearProducto = require('@altertex/pro/repos/repositorioCrearProducto'); +const repositorioCrearVariante = require('@altertex/pro/repos/repositorioCrearVariante'); +const repositorioCrearOpcion = require('@altertex/pro/repos/repositorioCrearOpcion'); +const db = require('@altertex/util/bd/db'); + +exports.importarProductos = async (req, res) => { + console.dir(req.body, { depth: null }); + const idCliente = parseInt(req.user.clienteSeleccionado); + const productos = req.body; // Espera array de { producto, variantes } + + if (!Array.isArray(productos) || productos.length === 0) { + return res.status(400).json({ mensaje: 'No se recibieron productos válidos.' }); + } + + const errores = []; + let conexion = null; + + try { + conexion = await db.getConnection(); + await conexion.beginTransaction(); + + for (let im = 0; im < productos.length; im++) { + const { producto, variantes } = productos[im]; + const fila = im + 1; + + const errorProducto = validarProducto(producto); + if (errorProducto) { + errores.push({ fila, error: errorProducto.error }); + continue; + } + + if (!Array.isArray(variantes) || variantes.length === 0) { + errores.push({ fila, error: 'Producto sin variantes válidas.' }); + continue; + } + + const idProducto = await repositorioCrearProducto.crearProducto(idCliente, producto); + if (!idProducto) { + errores.push({ fila, error: 'Error al crear producto.' }); + continue; + } + + for (const variante of variantes) { + const errorVariante = validarVariante(variante); + if (errorVariante) { + errores.push({ fila, error: errorVariante.error }); + continue; + } + + const idVariante = await repositorioCrearVariante.crearVariante(idProducto, variante); + if (!idVariante) { + errores.push({ fila, error: 'Error al crear variante.' }); + continue; + } + + const errorOpciones = validarOpciones(variante.opciones); + if (errorOpciones) { + errores.push({ fila, error: errorOpciones.error }); + continue; + } + + await repositorioCrearOpcion.crearOpcion(idVariante, variante.opciones); + } + } + + await conexion.commit(); + + return res.status(200).json({ + mensaje: 'Importación completada.', + errores: errores.length ? errores : null, + }); + } catch (err) { + if (conexion) await conexion.rollback(); + return res.status(500).json({ + mensaje: 'Error al importar productos.', + error: err.message, + }); + } finally { + if (conexion) conexion.release(); + } +}; diff --git a/Productos/Rutas/RutasIndividuales/importarProductos.routes.js b/Productos/Rutas/RutasIndividuales/importarProductos.routes.js new file mode 100644 index 00000000..f5d18b25 --- /dev/null +++ b/Productos/Rutas/RutasIndividuales/importarProductos.routes.js @@ -0,0 +1,27 @@ +//RF26 Crea Producto - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF26 +const express = require('express'); +const ruteador = express.Router(); +const controlador = require('@altertex/pro/ctrl/importarProductos.controller'); +const revisarApiKey = require('@altertex/util/inter/revisarApiKey'); +const autorizarToken = require('@altertex/util/inter/autorizarToken'); +const verificarPermisos = require('@altertex/util/inter/verificarPermisos'); +const limitePeticionesDiarias = require('@altertex/util/inter/limitePeticiones'); + + +const PERMISOS = require('@altertex/util/const/permisos'); +const RUTAS = require('@altertex/util/const/rutas'); + +/** + * @swagger +**/ + +ruteador.post( + RUTAS.PRODUCTOS.IMPORTAR, + revisarApiKey(), + autorizarToken, + limitePeticionesDiarias, + verificarPermisos(PERMISOS.IMPORTAR_PRODUCTOS), + controlador.importarProductos +); + +module.exports = ruteador; diff --git a/Productos/Rutas/indexProductos.routes.js b/Productos/Rutas/indexProductos.routes.js index 47a7202e..b8ee2ea5 100644 --- a/Productos/Rutas/indexProductos.routes.js +++ b/Productos/Rutas/indexProductos.routes.js @@ -3,6 +3,7 @@ const ruteador = express.Router(); const rutaConsultarLista = require('@altertex/pro/rutasInd/consultarProductos.routes'); const rutaEliminar = require("@altertex/pro/rutasInd/eliminarProducto.routes"); const rutaCrearProducto = require('@altertex/pro/rutasInd/crearProducto.routes'); +const rutaImportarProductos = require('@altertex/pro/rutasInd/importarProductos.routes'); const RUTAS = require('@altertex/util/const/rutas'); @@ -13,4 +14,6 @@ ruteador.use(RUTAS.PRODUCTOS.BASE, rutaCrearProducto); // RF[30] Eliminar Producto - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF30] ruteador.use(RUTAS.PRODUCTOS.BASE, rutaEliminar); +ruteador.use(RUTAS.PRODUCTOS.BASE, rutaImportarProductos); + module.exports = ruteador; \ No newline at end of file diff --git a/Utilidades/Constantes/rutas.js b/Utilidades/Constantes/rutas.js index 92946212..534cebf9 100644 --- a/Utilidades/Constantes/rutas.js +++ b/Utilidades/Constantes/rutas.js @@ -40,6 +40,7 @@ module.exports = { CONSULTAR_LISTA: '/consultar-lista', CREAR: '/crear', ELIMINAR_PRODUCTO: '/eliminar', + IMPORTAR: '/importar', }, PROVEEDORES: { BASE: '/proveedores', From 1299480bda6b1ecfc2ff773ed958645ff8c9304d Mon Sep 17 00:00:00 2001 From: Diego Alfaro Pinto Date: Mon, 26 May 2025 17:21:01 -0600 Subject: [PATCH 019/116] feat: agregar funcionalidad para leer rol --- Roles/Controladores/consultarDetalleRol.js | 69 +++++++++++++++++++ .../Repositorios/repositorioDetalleRol.js | 29 ++++++++ .../consultarDetalleRol.routes.js | 54 +++++++++++++++ Roles/Rutas/indexRoles.routes.js | 6 +- Utilidades/Constantes/consultasRoles.js | 56 ++++++++++----- Utilidades/Constantes/rutas.js | 5 +- 6 files changed, 198 insertions(+), 21 deletions(-) create mode 100644 Roles/Controladores/consultarDetalleRol.js create mode 100644 Roles/Datos/Repositorios/repositorioDetalleRol.js create mode 100644 Roles/Rutas/RutasIndividuales/consultarDetalleRol.routes.js diff --git a/Roles/Controladores/consultarDetalleRol.js b/Roles/Controladores/consultarDetalleRol.js new file mode 100644 index 00000000..a402fe08 --- /dev/null +++ b/Roles/Controladores/consultarDetalleRol.js @@ -0,0 +1,69 @@ +const repositorio = require('@altertex/rol/repos/repositorioDetalleRol'); +const MENSAJES_ROLES = require('@altertex/util/const/mensajesRoles'); + +/** + * RF8 - Leer rol + * Documentación del requisito funcional: + * https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF8 + * + * @function consultarDetalle + * @async + * @param {object} req - Objeto de solicitud HTTP (Request). + * @param {object} res - Objeto de respuesta HTTP (Response). + * @returns {Response} Respuesta HTTP con los detalles del rol solicitado. + * + * @description + * Este controlador obtiene el detalle de un rol: nombre, descripción, + * número de usuarios asociados y permisos relacionados. + */ +exports.consultarDetalle = async (req, res) => { + try { + const { idRol } = req.query; + + // Validación: verificar que se proporcione un ID válido + if (!idRol || isNaN(Number(idRol))) { + return res + .status(MENSAJES_ROLES.PARAMETROS_INVALIDOS.codigo) + .json({ mensaje: MENSAJES_ROLES.PARAMETROS_INVALIDOS.mensaje }); + } + + // Se consulta al repositorio de roles para obtener el detalle por ID. + const resultado = await repositorio.obtenerDetalleRol(Number(idRol)); + + // Validación: si no se encontró el rol, se responde con mensaje de "sin resultados". + if (!resultado || resultado.length === 0) { + return res + .status(MENSAJES_ROLES.SIN_RESULTADOS.codigo) + .json({ mensaje: MENSAJES_ROLES.SIN_RESULTADOS.mensaje }); + } + + // Extrae los datos generales del rol desde la primera fila + const { nombreRol, descripcionRol, totalUsuarios } = resultado[0]; + + // Construye arreglo de permisos (omite si no tiene permisos) + const permisos = resultado + .filter(permiso => permiso.idPermiso !== null) + .map(permiso => ({ + id: permiso.idPermiso, + nombre: permiso.nombrePermiso, + descripcion: permiso.descripcionPermiso, + })); + + // Respuesta exitosa con datos + return res.status(MENSAJES_ROLES.CONSULTA_EXITOSA.codigo).json({ + mensaje: MENSAJES_ROLES.CONSULTA_EXITOSA.mensaje, + rol: { + idRol: Number(idRol), + nombre: nombreRol, + descripcion: descripcionRol, + totalUsuarios, + permisos, + }, + }); + } catch { + // Error inesperado en el servidor + return res + .status(MENSAJES_ROLES.ERROR_CONSULTAR_ROLES.codigo) + .json({ mensaje: MENSAJES_ROLES.ERROR_CONSULTAR_ROLES.mensaje }); + } +}; \ No newline at end of file diff --git a/Roles/Datos/Repositorios/repositorioDetalleRol.js b/Roles/Datos/Repositorios/repositorioDetalleRol.js new file mode 100644 index 00000000..b950287a --- /dev/null +++ b/Roles/Datos/Repositorios/repositorioDetalleRol.js @@ -0,0 +1,29 @@ +const correrQuery = require('@altertex/util/ser/correrQuery'); +const CONSULTAS_ROLES = require('@altertex/util/const/consultasRoles'); +/** + * RF8 - Leer detalle de un rol + * Documentación del requisito funcional: + * https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF8 + * + * @async + * @function obtenerDetalleRol + * @param {number} idRol - ID del rol que se desea consultar. + * @returns {Promise>} Arreglo de objetos con datos del rol y sus permisos asociados. + * + * @throws {Error} Si ocurre un error en la consulta a la base de datos. + */ +exports.obtenerDetalleRol = async (idRol) => { + const query = CONSULTAS_ROLES.OBTENER_DETALLE_ROL; + + try { + const resultado = await correrQuery(query, [idRol]); + + if (!resultado || resultado.length === 0) { + throw new Error('Rol no encontrado'); + } + + return resultado; + } catch { + throw new Error('Error al consultar el detalle del rol'); + } +}; \ No newline at end of file diff --git a/Roles/Rutas/RutasIndividuales/consultarDetalleRol.routes.js b/Roles/Rutas/RutasIndividuales/consultarDetalleRol.routes.js new file mode 100644 index 00000000..027bbac2 --- /dev/null +++ b/Roles/Rutas/RutasIndividuales/consultarDetalleRol.routes.js @@ -0,0 +1,54 @@ +const express = require('express'); +const ruteador = express.Router(); + +const controlador = require('@altertex/rol/ctrl/consultarDetalleRol.controller'); +const revisarApiKey = require('@altertex/util/inter/revisarApiKey'); +const autorizarToken = require('@altertex/util/inter/autorizarToken'); +const verificarPermisos = require('@altertex/util/inter/verificarPermisos'); +const validarYSanitizar = require('@altertex/util/inter/validarYSanitizar'); +const limitePeticionesDiarias = require('@altertex/util/inter/limitePeticiones'); + +const PERMISOS = require('@altertex/util/const/permisos'); +const RUTAS = require('@altertex/util/const/rutas'); + +/** + * RF## - Leer detalle de un rol - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF## + */ + +/** + * @swagger + * /api/roles/leer: + * get: + * summary: Obtener detalle de un rol + * description: Devuelve nombre, descripción, cantidad de usuarios y permisos de un rol específico. + * tags: [Roles] + * security: + * - ApiKeyAuth: [] + * parameters: + * - in: query + * name: idRol + * required: true + * schema: + * type: integer + * description: ID del rol a consultar. + * responses: + * 200: + * description: Detalle del rol obtenido exitosamente. + * 400: + * description: Parámetros inválidos. + * 404: + * description: Rol no encontrado. + * 500: + * description: Error interno del servidor. + */ +ruteador.get( + RUTAS.ROLES.LEER_ROL, + validarYSanitizar, + revisarApiKey(), + autorizarToken, + limitePeticionesDiarias, + verificarPermisos(PERMISOS.LEER_ROL), + controlador.consultarDetalle, +); + +module.exports = ruteador; \ No newline at end of file diff --git a/Roles/Rutas/indexRoles.routes.js b/Roles/Rutas/indexRoles.routes.js index 6ea28210..1fa63569 100644 --- a/Roles/Rutas/indexRoles.routes.js +++ b/Roles/Rutas/indexRoles.routes.js @@ -21,6 +21,8 @@ const rutasObtenerOpcionesRol = require('@altertex/rol/rutasInd/obtenerOpcionesR const rutasEliminarRol = require('@altertex/rol/rutasInd/eliminarRol.routes'); +const rutasConsultarDetalle = require('@altertex/rol/rutasInd/consultarDetalleRol.routes'); + // Importación del archivo de constantes donde están definidas las rutas base del sistema. const RUTAS = require('@altertex/util/const/rutas'); @@ -39,5 +41,7 @@ ruteador.use(RUTAS.ROLES.BASE, rutasObtenerOpcionesRol); ruteador.use(RUTAS.ROLES.BASE, rutasEliminarRol); +ruteador.use(RUTAS.ROLES.BASE, rutasConsultarDetalle); + // Exporta el enrutador para ser utilizado en el archivo principal de rutas de la aplicación (por ejemplo: app.js). -module.exports = ruteador; +module.exports = ruteador; \ No newline at end of file diff --git a/Utilidades/Constantes/consultasRoles.js b/Utilidades/Constantes/consultasRoles.js index 01d1fc37..d4f72f9c 100644 --- a/Utilidades/Constantes/consultasRoles.js +++ b/Utilidades/Constantes/consultasRoles.js @@ -28,38 +28,58 @@ module.exports = { * Agrupa los resultados por `idRol` para consolidar la información por rol. */ OBTENER_LISTA: ` - SELECT r.idRol, r.nombre, r.descripcion, COUNT(ur.idUsuario) AS totalUsuarios - FROM rol r - LEFT JOIN usuario_rol ur ON r.idRol = ur.idRol - GROUP BY r.idRol; + SELECT r.idRol, r.nombre, r.descripcion, COUNT(ur.idUsuario) AS totalUsuarios + FROM rol r + LEFT JOIN usuario_rol ur ON r.idRol = ur.idRol + GROUP BY r.idRol; `, VERIFICAR_NOMBRE_ROL: ` - SELECT idRol FROM rol WHERE nombre = ? LIMIT 1`, + SELECT idRol + FROM rol + WHERE nombre = ? LIMIT 1`, VERIFICAR_PERMISO: ` - SELECT idPermiso FROM permiso WHERE idPermiso = ? LIMIT 1`, + SELECT idPermiso + FROM permiso + WHERE idPermiso = ? LIMIT 1`, INSERTAR_ROL: ` - INSERT INTO rol (nombre, descripcion) - VALUES (?, ?)`, + INSERT INTO rol (nombre, descripcion) + VALUES (?, ?)`, INSERTAR_ROL_PERMISO: ` - INSERT INTO rol_permiso (idRol, idPermiso) - VALUES (?, ?)`, + INSERT INTO rol_permiso (idRol, idPermiso) + VALUES (?, ?)`, OBTENER_PERMISOS_POR_CLIENTE: ` - SELECT idPermiso AS id, nombre FROM permiso; + SELECT idPermiso AS id, nombre + FROM permiso; `, ELIMINAR_ROL: ` - DELETE FROM rol - WHERE idRol IN (__IDS__); + DELETE + FROM rol + WHERE idRol IN (__IDS__); `, VALIDAR_ROL_SIN_USUARIOS: ` - SELECT COUNT(*) AS cantidad - FROM usuario_rol - WHERE idRol IN (__IDS__); + SELECT COUNT(*) AS cantidad + FROM usuario_rol + WHERE idRol IN (__IDS__); + `, + OBTENER_DETALLE_ROL: ` + SELECT r.idRol, + r.nombre AS nombreRol, + r.descripcion AS descripcionRol, + (SELECT COUNT(*) + FROM usuario_rol ur + WHERE ur.idRol = r.idRol) AS totalUsuarios, + p.idPermiso, + p.nombre AS nombrePermiso, + p.descripcion AS descripcionPermiso + FROM rol r + LEFT JOIN rol_permiso rp ON r.idRol = rp.idRol + LEFT JOIN permiso p ON rp.idPermiso = p.idPermiso + WHERE r.idRol = ?; `, - -}; +}; \ No newline at end of file diff --git a/Utilidades/Constantes/rutas.js b/Utilidades/Constantes/rutas.js index 92946212..8c45804c 100644 --- a/Utilidades/Constantes/rutas.js +++ b/Utilidades/Constantes/rutas.js @@ -8,7 +8,7 @@ module.exports = { CERRAR_SESION: '/cerrar-sesion', USUARIO_AUTENTICADO: '/autenticar', ACTIVAR_2FA: '/activar-2fa', - VERIFICAR_2FA: '/verificar-2fa' + VERIFICAR_2FA: '/verificar-2fa', }, USUARIOS: { BASE: '/usuarios', @@ -87,6 +87,7 @@ module.exports = { OBTENER_OPCIONES: '/obtener-opciones', CONFIRMAR_CREACION: '/confirmar-creacion', ELIMINAR_ROL: '/eliminar', + LEER_ROL: '/leer', }, PEDIDOS: { BASE: '/pedidos', @@ -99,4 +100,4 @@ module.exports = { ACTUALIZAR: '/actualizar', }, API_DOCS: '/api-docs', -}; +}; \ No newline at end of file From 4c1751372432941910ff923303cf2cb13010dc56 Mon Sep 17 00:00:00 2001 From: Hiram <147564077+Hiram10tec@users.noreply.github.com> Date: Mon, 26 May 2025 18:36:57 -0600 Subject: [PATCH 020/116] fix(rol): cambio de nombre archivo de controller --- .../{consultarDetalleRol.js => consultarDetalleRol.controller.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Roles/Controladores/{consultarDetalleRol.js => consultarDetalleRol.controller.js} (100%) diff --git a/Roles/Controladores/consultarDetalleRol.js b/Roles/Controladores/consultarDetalleRol.controller.js similarity index 100% rename from Roles/Controladores/consultarDetalleRol.js rename to Roles/Controladores/consultarDetalleRol.controller.js From 30645ace1af0ce01e8b16f971893bffef8e34bae Mon Sep 17 00:00:00 2001 From: Hiram <147564077+Hiram10tec@users.noreply.github.com> Date: Tue, 27 May 2025 10:49:28 -0600 Subject: [PATCH 021/116] Feature(categorias):Leer-categoria --- .../consultarDetalleCategoria.controller.js | 41 +++++++++++++++++++ .../repositorioLeerDetalleCategoria.js | 35 ++++++++++++++++ .../consultarDetalleCategoria.routes.js | 22 ++++++++++ Categorias/Rutas/indexCategorias.routes.js | 2 + Utilidades/Constantes/consultasCategorias.js | 13 ++++++ Utilidades/Constantes/rutas.js | 2 +- 6 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 Categorias/Controladores/consultarDetalleCategoria.controller.js create mode 100644 Categorias/Datos/Repositorios/repositorioLeerDetalleCategoria.js create mode 100644 Categorias/Rutas/RutasIndividuales/consultarDetalleCategoria.routes.js diff --git a/Categorias/Controladores/consultarDetalleCategoria.controller.js b/Categorias/Controladores/consultarDetalleCategoria.controller.js new file mode 100644 index 00000000..1bcd4163 --- /dev/null +++ b/Categorias/Controladores/consultarDetalleCategoria.controller.js @@ -0,0 +1,41 @@ +const repositorio = require('@altertex/cat/repos/repositorioLeerDetalleCategoria'); +const MENSAJES_CATEGORIAS = require('@altertex/util/const/mensajesCategorias'); + +/** + * Consulta el detalle de una categoría de productos, incluyendo sus productos asociados. + * + * @function + * @async + * @param {Express.Request} req - Objeto de solicitud HTTP con `req.params.idCategoria`. + * @param {Express.Response} res - Objeto de respuesta HTTP para enviar el resultado. + * + * @returns {Promise} Devuelve una respuesta HTTP con el detalle de la categoría o un mensaje de error. + * + * @description + * Implementa el RF48: Leer categoría de productos. + * Si no se encuentra la categoría, devuelve código 404. + * Si ocurre un error inesperado, devuelve código 500. + * + * @see [RF48 - Documentación de requisitos](https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF48) + */ +exports.consultarDetalleCategoria = async (req, res) => { + const idCategoria = parseInt(req.params.idCategoria); + + try { + const resultado = await repositorio.leerDetalleCategoria(idCategoria); + + if (!resultado) { + return res + .status(MENSAJES_CATEGORIAS.CATEGORIA_NO_ENCONTRADA.codigo) + .json({ mensaje: MENSAJES_CATEGORIAS.CATEGORIA_NO_ENCONTRADA.mensaje }); + } + + return res + .status(MENSAJES_CATEGORIAS.CATEGORIA_OBTENIDA.codigo) + .json({ mensaje: MENSAJES_CATEGORIAS.CATEGORIA_OBTENIDA.mensaje, categoria: resultado }); + } catch { + return res + .status(MENSAJES_CATEGORIAS.ERROR_OBTENER_CATEGORIA.codigo) + .json({ mensaje: MENSAJES_CATEGORIAS.ERROR_OBTENER_CATEGORIA.mensaje }); + } +}; \ No newline at end of file diff --git a/Categorias/Datos/Repositorios/repositorioLeerDetalleCategoria.js b/Categorias/Datos/Repositorios/repositorioLeerDetalleCategoria.js new file mode 100644 index 00000000..63b0ca0e --- /dev/null +++ b/Categorias/Datos/Repositorios/repositorioLeerDetalleCategoria.js @@ -0,0 +1,35 @@ +const correrQuery = require('@altertex/util/ser/correrQuery'); +const CONSULTAS = require('@altertex/util/const/consultasCategorias'); + +/** + * Consulta el detalle de una categoría y sus productos asociados. + * + * @param {number} idCategoria - ID de la categoría a consultar. + * @returns {Promise} Objeto con la información de la categoría y sus productos, o null si no existe. + * + * @throws {Error} Si ocurre un error al ejecutar la consulta. + * + * @see [RF48 - Documentación de requisitos](https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF48) + */ +exports.leerDetalleCategoria = async (idCategoria) => { + const query = CONSULTAS.LEER_DETALLE_CATEGORIA; + const resultados = await correrQuery(query, [idCategoria]); + + if (!resultados || resultados.length === 0) return null; + + const { nombreCategoria, descripcion } = resultados[0]; + + const productos = resultados + .filter(resul => resul.idProducto !== null) + .map(produc => ({ + idProducto: produc.idProducto, + nombreComun: produc.nombreComun, + })); + + return { + idCategoria, + nombreCategoria, + descripcion, + productos, + }; +}; \ No newline at end of file diff --git a/Categorias/Rutas/RutasIndividuales/consultarDetalleCategoria.routes.js b/Categorias/Rutas/RutasIndividuales/consultarDetalleCategoria.routes.js new file mode 100644 index 00000000..0b755eaf --- /dev/null +++ b/Categorias/Rutas/RutasIndividuales/consultarDetalleCategoria.routes.js @@ -0,0 +1,22 @@ +/** + * RF48 Leer categoría de productos - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF48 + */ + +const express = require('express'); +const ruteador = express.Router(); +const controlador = require('@altertex/cat/ctrl/consultarDetalleCategoria.controller'); +const revisarApiKey = require('@altertex/util/inter/revisarApiKey'); +const autorizarToken = require('@altertex/util/inter/autorizarToken'); +const verificarPermisos = require('@altertex/util/inter/verificarPermisos'); +const PERMISOS = require('@altertex/util/const/permisos'); +const RUTAS = require('@altertex/util/const/rutas'); + +ruteador.get( + `${RUTAS.CATEGORIAS.LEER}/:idCategoria`, + revisarApiKey(), + autorizarToken, + verificarPermisos(PERMISOS.LEER_CATEGORIA_PRODUCTOS), + controlador.consultarDetalleCategoria +); + +module.exports = ruteador; \ No newline at end of file diff --git a/Categorias/Rutas/indexCategorias.routes.js b/Categorias/Rutas/indexCategorias.routes.js index 5555b343..35f1bef5 100644 --- a/Categorias/Rutas/indexCategorias.routes.js +++ b/Categorias/Rutas/indexCategorias.routes.js @@ -3,11 +3,13 @@ const ruteador = express.Router(); const rutasConsultarListaCategorias = require('@altertex/cat/rutasInd/consultarListaCategorias.routes'); const rutasCrearCategoria = require('@altertex/cat/rutasInd/crearCategoria.routes'); const rutasEliminarCategoria = require('@altertex/cat/rutasInd/eliminarCategoria.routes'); +const rutasLeerCategoria = require('@altertex/cat/rutasInd/consultarDetalleCategoria.routes'); const RUTAS = require('@altertex/util/const/rutas'); ruteador.use(RUTAS.CATEGORIAS.BASE, rutasConsultarListaCategorias); ruteador.use(RUTAS.CATEGORIAS.BASE, rutasCrearCategoria); ruteador.use(RUTAS.CATEGORIAS.BASE, rutasEliminarCategoria); +ruteador.use(RUTAS.CATEGORIAS.BASE, rutasLeerCategoria); module.exports = ruteador; diff --git a/Utilidades/Constantes/consultasCategorias.js b/Utilidades/Constantes/consultasCategorias.js index 6890d77a..7715e286 100644 --- a/Utilidades/Constantes/consultasCategorias.js +++ b/Utilidades/Constantes/consultasCategorias.js @@ -47,4 +47,17 @@ module.exports = { FROM producto WHERE idProducto IN (?); `, + + LEER_DETALLE_CATEGORIA: ` + SELECT + c.idCategoria, + c.nombreCategoria, + c.descripcion, + p.idProducto, + p.nombreComun + FROM categoria c + LEFT JOIN categoria_producto cp ON c.idCategoria = cp.idCategoria + LEFT JOIN producto p ON cp.idProducto = p.idProducto + WHERE c.idCategoria = ?; + `, }; diff --git a/Utilidades/Constantes/rutas.js b/Utilidades/Constantes/rutas.js index 8c45804c..11d9254e 100644 --- a/Utilidades/Constantes/rutas.js +++ b/Utilidades/Constantes/rutas.js @@ -25,7 +25,7 @@ module.exports = { CONSULTAR_LISTA_USUARIOS: '/consultar-lista-usuarios', CREAR: '/crear', ELIMINAR_USUARIOS: '/eliminar-usuarios', - LEER: '/consultar-usuario', + LEER: '/leer', }, EVENTOS: { BASE: '/eventos', From af57888a2eadabca9850fcbe7169be76a5766c04 Mon Sep 17 00:00:00 2001 From: Krlos7121 Date: Tue, 27 May 2025 11:41:06 -0600 Subject: [PATCH 022/116] feature: backend crear empleado --- .../Controladores/crearEmpleado.controller.js | 17 + .../Repositorios/repositorioCrearEmpleado.js | 53 +++ .../actualizarEmpleado.routes.js | 158 +++----- .../RutasIndividuales/crearEmpleado.routes.js | 204 ++++++++++ Utilidades/Constantes/consultasEmpleados.js | 13 + Utilidades/Constantes/mensajesEmpleados.js | 8 + Utilidades/Constantes/rutas.js | 3 +- package-lock.json | 378 +++++++++--------- 8 files changed, 531 insertions(+), 303 deletions(-) create mode 100644 Empleados/Controladores/crearEmpleado.controller.js create mode 100644 Empleados/Datos/Repositorios/repositorioCrearEmpleado.js create mode 100644 Empleados/Rutas/RutasIndividuales/crearEmpleado.routes.js diff --git a/Empleados/Controladores/crearEmpleado.controller.js b/Empleados/Controladores/crearEmpleado.controller.js new file mode 100644 index 00000000..4cded3f6 --- /dev/null +++ b/Empleados/Controladores/crearEmpleado.controller.js @@ -0,0 +1,17 @@ +const MENSAJES = require('@altertex/util/const/mensajesEmpleados'); +const repositorio = require('@altertex/emp/repos/repositorioCrearEmpleado'); +//RF[16] Crear empleado - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF16] + +/** + * Controlador para crear un nuevo empleado. + * Este endpoint recibe un objeto con la información del nuevo empleado + * y usa su repositorio para insertar el nuevo registro en la base de datos. + * + * @function crearEmpleado + * @async + * @param {Express.Request} req - Objeto de solicitud HTTP de Express. + * @param {object} req.body - Cuerpo de la solicitud. + * @param {object} req.body.empleado - Información del nuevo empleado a crear. + * @param {Express.Response} res - Objeto de respuesta HTTP de Express. + * @returns {Promise} Retorna una respuesta JSON indicando éxito o un error. + */ diff --git a/Empleados/Datos/Repositorios/repositorioCrearEmpleado.js b/Empleados/Datos/Repositorios/repositorioCrearEmpleado.js new file mode 100644 index 00000000..b1959f78 --- /dev/null +++ b/Empleados/Datos/Repositorios/repositorioCrearEmpleado.js @@ -0,0 +1,53 @@ +const correrQuery = require('@altertex/util/ser/correrQuery'); +const MENSAJES = require('@altertex/util/const/mensajesEmpleados'); +const CONSULTAS_EMPLEADOS = require('@altertex/util/const/consultasEmpleados'); + +//RF[16] Crear empleado - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF16] + +/** + * Repositorio para insertar un nuevo empleado en la base de datos. + * + * Este repositorio recibe un array con la información del nuevo empleado + * y realiza la inserción en la base de datos. + * + * @function insertarEmpleado + * @async + * @param {Array<{ idEmpleado: number, idUsuario: number, idCliente: number, numeroEmergencia: + * number, areaTrabajo: string, posicion: string, + * cantidadPuntos: number, antiguedad: Date}>} datos - Información del nuevo empleado a insertar. + * @throws {Error} Si el arreglo está vacío o si ocurre un error en la base de datos. + * @returns {Promise} Promesa que se resuelve cuando la inserción ha sido exitosa. + */ + +exports.insertarEmpleado = async (datos) => { + if (!Array.isArray(datos) || datos.length === 0) { + throw new Error('Sin datos para insertar.'); + } + try { + await Promise.all( + datos.map( + ({ + idUsuario, + idCliente, + numeroEmergencia, + areaTrabajo, + posicion, + cantidadPuntos, + antiguedad, + }) => { + return correrQuery(CONSULTAS_EMPLEADOS.INSERTAR_EMPLEADO, [ + idUsuario, + idCliente, + numeroEmergencia, + areaTrabajo, + posicion, + cantidadPuntos, + antiguedad, + ]); + } + ) + ); + } catch (error) { + throw new Error(MENSAJES.ERROR_CREAR.mensaje); + } +}; diff --git a/Empleados/Rutas/RutasIndividuales/actualizarEmpleado.routes.js b/Empleados/Rutas/RutasIndividuales/actualizarEmpleado.routes.js index 544300ab..5b1496aa 100644 --- a/Empleados/Rutas/RutasIndividuales/actualizarEmpleado.routes.js +++ b/Empleados/Rutas/RutasIndividuales/actualizarEmpleado.routes.js @@ -10,14 +10,13 @@ const revisarPermisos = require('@altertex/util/inter/verificarPermisos'); const validarYSanitizar = require('@altertex/util/inter/validarYSanitizar'); const limitePeticionesDiarias = require('@altertex/util/inter/limitePeticiones'); - //RF[19] Actualizar Empleado - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF19] /** * @swagger * /api/empleados/actualizar: * put: - * summary: Actualiza la información de un empleado. + * summary: Actualiza la información de uno o varios empleados. * tags: * - Empleados * security: @@ -28,115 +27,48 @@ const limitePeticionesDiarias = require('@altertex/util/inter/limitePeticiones') * content: * application/json: * schema: - * oneOf: - * - type: object - * description: Información del empleado a actualizar directamente en el body - * required: - * - idEmpleado - * - numeroEmergencia - * - areaTrabajo - * - posicion - * - cantidadPuntos - * - antiguedad - * properties: - * id: - * type: integer - * example: 50 - * idEmpleado: - * type: integer - * example: 50 - * idUsuario: - * type: integer - * example: 30 - * nombreCompleto: - * type: string - * example: "Angel Romero" - * correoElectronico: - * type: string - * example: "aromero@google.com" - * numeroEmergencia: - * type: string - * example: "9876543214" - * areaTrabajo: - * type: string - * example: "Ventas" - * posicion: - * type: string - * example: "Auxiliar" - * cantidadPuntos: - * type: integer - * example: 2 - * antiguedad: - * type: string - * example: "2000-02-10" - * - type: object - * properties: - * cambios: - * oneOf: - * - type: object - * description: Objeto único con información del empleado - * required: - * - idEmpleado - * - numeroEmergencia - * - areaTrabajo - * - posicion - * - cantidadPuntos - * - antiguedad - * properties: - * idEmpleado: - * type: integer - * example: 50 - * idUsuario: - * type: integer - * example: 30 - * numeroEmergencia: - * type: string - * example: "9876543214" - * areaTrabajo: - * type: string - * example: "Ventas" - * posicion: - * type: string - * example: "Auxiliar" - * cantidadPuntos: - * type: integer - * example: 2 - * antiguedad: - * type: string - * example: "2000-02-10" - * - type: array - * description: Array de objetos con información de empleados - * items: - * type: object - * required: - * - idEmpleado - * - numeroEmergencia - * - areaTrabajo - * - posicion - * - cantidadPuntos - * - antiguedad - * properties: - * idEmpleado: - * type: integer - * example: 50 - * idUsuario: - * type: integer - * example: 30 - * numeroEmergencia: - * type: string - * example: "9876543214" - * areaTrabajo: - * type: string - * example: "Ventas" - * posicion: - * type: string - * example: "Auxiliar" - * cantidadPuntos: - * type: integer - * example: 2 - * antiguedad: - * type: string - * example: "2000-02-10" + * type: array + * description: Array de objetos con información de empleados a actualizar + * items: + * type: object + * required: + * - idEmpleado + * - numeroEmergencia + * - areaTrabajo + * - posicion + * - cantidadPuntos + * - antiguedad + * properties: + * id: + * type: integer + * example: 50 + * idEmpleado: + * type: integer + * example: 50 + * idUsuario: + * type: integer + * example: 30 + * nombreCompleto: + * type: string + * example: "Angel Romero" + * correoElectronico: + * type: string + * example: "aromero@google.com" + * numeroEmergencia: + * type: string + * example: "9876543214" + * areaTrabajo: + * type: string + * example: "Ventas" + * posicion: + * type: string + * example: "Auxiliar" + * cantidadPuntos: + * type: integer + * example: 2 + * antiguedad: + * type: string + * example: "2000-02-10" * responses: * 200: * description: Información del empleado actualizada correctamente. @@ -166,12 +98,12 @@ const limitePeticionesDiarias = require('@altertex/util/inter/limitePeticiones') * - lang: JavaScript * label: cURL * source: | - * # Ejemplo enviando datos directamente + * # Ejemplo enviando un array de empleados * curl -X PUT "https://tu-api.com/api/empleados/actualizar" \ * -H "x-api-key: TU_API_KEY" \ * -H "Authorization: Bearer TU_TOKEN" \ * -H "Content-Type: application/json" \ - * -d '{"id":50,"idUsuario":30,"nombreCompleto":"Angel Romero","correoElectronico":"aromero@google.com","numeroEmergencia":"9876543214","areaTrabajo":"Ventas","posicion":"Auxiliar","cantidadPuntos":2,"antiguedad":"2000-02-10","idEmpleado":50}' + * -d '[{"id":50,"idUsuario":30,"nombreCompleto":"Angel Romero","correoElectronico":"aromero@google.com","numeroEmergencia":"9876543214","areaTrabajo":"Ventas","posicion":"Auxiliar","cantidadPuntos":2,"antiguedad":"2000-02-10","idEmpleado":50}]' */ ruteador.put( diff --git a/Empleados/Rutas/RutasIndividuales/crearEmpleado.routes.js b/Empleados/Rutas/RutasIndividuales/crearEmpleado.routes.js new file mode 100644 index 00000000..9cffd0bb --- /dev/null +++ b/Empleados/Rutas/RutasIndividuales/crearEmpleado.routes.js @@ -0,0 +1,204 @@ +const express = require('express'); +const ruteador = express.Router(); + +const PERMISOS = require('@altertex/util/const/permisos'); +const RUTAS = require('@altertex/util/const/rutas'); +const controlador = require('@altertex/emp/ctrl/actualizarEmpleado.controller'); +const revisarApiKey = require('@altertex/util/inter/revisarApiKey'); +const autorizarToken = require('@altertex/util/inter/autorizarToken'); +const revisarPermisos = require('@altertex/util/inter/verificarPermisos'); +const validarYSanitizar = require('@altertex/util/inter/validarYSanitizar'); +const limitePeticionesDiarias = require('@altertex/util/inter/limitePeticiones'); + +//RF[16] Crear Empleado - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF16] + +/** + * @swagger + * /api/empleados/crear: + * post: + * summary: Crea un nuevo empleado. + * tags: + * - Empleados + * security: + * - ApiKeyAuth: [] + * - BearerAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * oneOf: + * - type: object + * description: Información del nuevo empleado a crear directamente en el body + * required: + * - idUsuario + * - idCliente + * - nombreCompleto + * - correoElectronico + * - numeroEmergencia + * - areaTrabajo + * - posicion + * - cantidadPuntos + * - antiguedad + * properties: + * idUsuario: + * type: integer + * example: 30 + * idCliente: + * type: integer + * example: 10 + * nombreCompleto: + * type: string + * example: "Carlos Pérez" + * correoElectronico: + * type: string + * example: "cperez@google.com" + * numeroEmergencia: + * type: string + * example: "9876543214" + * areaTrabajo: + * type: string + * example: "Producción" + * posicion: + * type: string + * example: "Operador" + * cantidadPuntos: + * type: integer + * example: 0 + * antiguedad: + * type: string + * example: "2024-01-01" + * - type: object + * properties: + * empleados: + * oneOf: + * - type: object + * description: Array único con información del empleado + * required: + * - idUsuario + * - idCliente + * - nombreCompleto + * - correoElectronico + * - numeroEmergencia + * - areaTrabajo + * - posicion + * - cantidadPuntos + * - antiguedad + * properties: + * idUsuario: + * type: integer + * example: 30 + * idCliente: + * type: integer + * example: 10 + * nombreCompleto: + * type: string + * example: "Carlos Pérez" + * correoElectronico: + * type: string + * example: "cperez@google.com" + * numeroEmergencia: + * type: string + * example: "9876543214" + * areaTrabajo: + * type: string + * example: "Producción" + * posicion: + * type: string + * example: "Operador" + * cantidadPuntos: + * type: integer + * example: 0 + * antiguedad: + * type: string + * example: "2024-01-01" + * - type: array + * description: Array de objetos con información de empleados + * items: + * type: object + * required: + * - idUsuario + * - idCliente + * - nombreCompleto + * - correoElectronico + * - numeroEmergencia + * - areaTrabajo + * - posicion + * - cantidadPuntos + * - antiguedad + * properties: + * idUsuario: + * type: integer + * example: 30 + * idCliente: + * type: integer + * example: 10 + * nombreCompleto: + * type: string + * example: "Carlos Pérez" + * correoElectronico: + * type: string + * example: "cperez@google.com" + * numeroEmergencia: + * type: string + * example: "9876543214" + * areaTrabajo: + * type: string + * example: "Producción" + * posicion: + * type: string + * example: "Operador" + * cantidadPuntos: + * type: integer + * example: 0 + * antiguedad: + * type: string + * example: "2024-01-01" + * responses: + * 201: + * description: Empleado creado correctamente. + * content: + * application/json: + * schema: + * type: object + * properties: + * mensaje: + * type: string + * example: "Empleado creado exitosamente." + * datos: + * type: array + * items: + * type: object + * 400: + * description: Error en los datos enviados o en la creación. + * content: + * application/json: + * schema: + * type: object + * properties: + * mensaje: + * type: string + * example: "Error al crear empleado" + * x-codeSamples: + * - lang: JavaScript + * label: cURL + * source: | + * # Ejemplo enviando datos directamente + * curl -X POST "https://tu-api.com/api/empleados/crear" \ + * -H "x-api-key: TU_API_KEY" \ + * -H "Authorization: Bearer TU_TOKEN" \ + * -H "Content-Type: application/json" \ + * -d '{"idUsuario":30,"idCliente":10,"nombreCompleto":"Carlos Pérez","correoElectronico":"cperez@google.com","numeroEmergencia":"9876543214","areaTrabajo":"Producción","posicion":"Operador","cantidadPuntos":0,"antiguedad":"2024-01-01"}' + */ + +ruteador.post( + RUTAS.EMPLEADOS.CREAR, + revisarApiKey, + autorizarToken, + revisarPermisos([PERMISOS.EMPLEADOS.CREAR]), + validarYSanitizar.validarCrearEmpleado, + limitePeticionesDiarias.limitarPeticionesDiarias, + controlador.crearEmpleado +); + +module.exports = ruteador; diff --git a/Utilidades/Constantes/consultasEmpleados.js b/Utilidades/Constantes/consultasEmpleados.js index 38e3b382..439c35b3 100644 --- a/Utilidades/Constantes/consultasEmpleados.js +++ b/Utilidades/Constantes/consultasEmpleados.js @@ -24,4 +24,17 @@ module.exports = { numeroEmergencia = ?, areaTrabajo = ?, posicion = ?, cantidadPuntos = ?, antiguedad = ? WHERE idEmpleado = ?; `, + OBTENER_ULTIMO_ID_EMPLEADO: ` + SELECT idEmpleado FROM empleado ORDER BY idEmpleado DESC LIMIT 1; + `, + CONSULTAR_ID_VALIDO: ` + SELECT + CASE + WHEN NOT EXISTS (SELECT 1 FROM usuarios WHERE idUsuario = ?) + THEN 'No hay ningún usuario registrado bajo este ID' + WHEN EXISTS (SELECT 1 FROM empleado WHERE idUsuario = ?) + THEN 'Este usuario ya está registrado como empleado, revisa de nuevo el ID a usar' + ELSE 'OK' + END AS resultado; + `, }; diff --git a/Utilidades/Constantes/mensajesEmpleados.js b/Utilidades/Constantes/mensajesEmpleados.js index f67fd574..3a06f8da 100644 --- a/Utilidades/Constantes/mensajesEmpleados.js +++ b/Utilidades/Constantes/mensajesEmpleados.js @@ -1,5 +1,9 @@ module.exports = { // 200 - OK + EXITO_CREAR: { + codigo: 200, + mensaje: 'Empleado agregado exitosamente.', + }, CONSULTA_EXITOSA: { codigo: 200, mensaje: 'Lista de empleados obtenida exitosamente.', @@ -28,6 +32,10 @@ module.exports = { codigo: 400, mensaje: 'Error al actualizar', }, + ERROR_CREAR: { + codigo: 400, + mensaje: 'Error al crear', + }, // 403 - Forbidden PERMISO_DENEGADO: { diff --git a/Utilidades/Constantes/rutas.js b/Utilidades/Constantes/rutas.js index 92946212..40effc7f 100644 --- a/Utilidades/Constantes/rutas.js +++ b/Utilidades/Constantes/rutas.js @@ -8,7 +8,7 @@ module.exports = { CERRAR_SESION: '/cerrar-sesion', USUARIO_AUTENTICADO: '/autenticar', ACTIVAR_2FA: '/activar-2fa', - VERIFICAR_2FA: '/verificar-2fa' + VERIFICAR_2FA: '/verificar-2fa', }, USUARIOS: { BASE: '/usuarios', @@ -64,6 +64,7 @@ module.exports = { }, EMPLEADOS: { BASE: '/empleados', + CREAR: '/crear', CONSULTAR_LISTA: '/consultar-lista', CONSULTAR_GRUPO: '/consultar-grupo', ELIMINAR_GRUPO: '/eliminar-grupo', diff --git a/package-lock.json b/package-lock.json index 23c6c96a..c7e96397 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3841,13 +3841,13 @@ } }, "node_modules/@swagger-api/apidom-ast": { - "version": "1.0.0-beta.37", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ast/-/apidom-ast-1.0.0-beta.37.tgz", - "integrity": "sha512-PeFTmtt1TGd5lks48JWcTj3sIytifuQLXfN5xwbxhB98z1J/UvFUV7M+Vg6E/oJsvUaI7TyiaTcUAZgIal/57w==", + "version": "1.0.0-beta.39", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ast/-/apidom-ast-1.0.0-beta.39.tgz", + "integrity": "sha512-EWeSOtvI8XpbYMRkDyu4qAIlivhcplrskpau2cbrWfXGBjrqEtmHqWlbJ9xoXJbNshbIcZ0Z77QdxicimGjs0w==", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-error": "^1.0.0-beta.37", + "@swagger-api/apidom-error": "^1.0.0-beta.39", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -3855,54 +3855,54 @@ } }, "node_modules/@swagger-api/apidom-core": { - "version": "1.0.0-beta.37", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-core/-/apidom-core-1.0.0-beta.37.tgz", - "integrity": "sha512-6ywaTg/95lZKV4c5cvPJ1vZWmUyN83G0qhe8VItsv8O7iFchaNRslgftoga03V6J8TGVlvAvWBQJ7WY/u2iUcQ==", + "version": "1.0.0-beta.39", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-core/-/apidom-core-1.0.0-beta.39.tgz", + "integrity": "sha512-tYZSVA+uDFvBJmnP104d8Qb/mye8B6ykNviohHAngHsy8ElcOPzSi5GKwwmJgf3taWzipMqWNM0ch5KytbXTqw==", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-ast": "^1.0.0-beta.37", - "@swagger-api/apidom-error": "^1.0.0-beta.37", + "@swagger-api/apidom-ast": "^1.0.0-beta.39", + "@swagger-api/apidom-error": "^1.0.0-beta.39", "@types/ramda": "~0.30.0", "minim": "~0.23.8", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", - "short-unique-id": "=5.2.0", + "short-unique-id": "^5.3.2", "ts-mixer": "^6.0.3" } }, "node_modules/@swagger-api/apidom-error": { - "version": "1.0.0-beta.37", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-error/-/apidom-error-1.0.0-beta.37.tgz", - "integrity": "sha512-goMIrcF02RFMW9uAfT0jWkp9gDNMOPqA2NJgb4bsEoBQL9uSXNnIMUEV3jF0fH4ZIanyqsar4ygznIKHC73Fdw==", + "version": "1.0.0-beta.39", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-error/-/apidom-error-1.0.0-beta.39.tgz", + "integrity": "sha512-vQ3xQaRQGP9kNNBEDcFCmUd2PT9rCtYdkCyqYWZMxHBm5dXSBC/dQaC5VN1DbqQygE16fSQC+c5sqOrwg5d5WQ==", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.20.7" } }, "node_modules/@swagger-api/apidom-json-pointer": { - "version": "1.0.0-beta.37", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-json-pointer/-/apidom-json-pointer-1.0.0-beta.37.tgz", - "integrity": "sha512-QZJScrxtG4J3oGdwOjV+X0bNdEh/Lggwy5zjnAFDajblRuqu6SADoIAEqrSsKPZzPwCuJ2N1haFpfJc6Z3c7Gg==", + "version": "1.0.0-beta.39", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-json-pointer/-/apidom-json-pointer-1.0.0-beta.39.tgz", + "integrity": "sha512-gPDNT+MCs/B1XYuNpmnz0rOHQ0ssN9YjVDqeGkX61v03BLJUF/JZKMo3J3FA2mgKb6ap+kRHzpzw5PpHLwRKAw==", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.0.0-beta.37", - "@swagger-api/apidom-error": "^1.0.0-beta.37", + "@swagger-api/apidom-core": "^1.0.0-beta.39", + "@swagger-api/apidom-error": "^1.0.0-beta.39", "@swaggerexpert/json-pointer": "^2.10.1" } }, "node_modules/@swagger-api/apidom-ns-api-design-systems": { - "version": "1.0.0-beta.37", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-api-design-systems/-/apidom-ns-api-design-systems-1.0.0-beta.37.tgz", - "integrity": "sha512-+/swYDxqNhjGxCdRG34hqmuTGNg/WCI+Je1CcmL+MlEvB2eiEAZ/KmhmvIES/P/6V1uPdFUsNOdQJ+oy8g7OJQ==", + "version": "1.0.0-beta.39", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-api-design-systems/-/apidom-ns-api-design-systems-1.0.0-beta.39.tgz", + "integrity": "sha512-MpdCb8KS3Tz1mGTrU0XC0Q2OcsrUWKB+buFPzLFOv0dU36ArARERX+Mz6aCJQ1CqnkFVM49uMe2NECO93ZR52w==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.0.0-beta.37", - "@swagger-api/apidom-error": "^1.0.0-beta.37", - "@swagger-api/apidom-ns-openapi-3-1": "^1.0.0-beta.37", + "@swagger-api/apidom-core": "^1.0.0-beta.39", + "@swagger-api/apidom-error": "^1.0.0-beta.39", + "@swagger-api/apidom-ns-openapi-3-1": "^1.0.0-beta.39", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -3910,15 +3910,15 @@ } }, "node_modules/@swagger-api/apidom-ns-arazzo-1": { - "version": "1.0.0-beta.37", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-arazzo-1/-/apidom-ns-arazzo-1-1.0.0-beta.37.tgz", - "integrity": "sha512-dYZckkAKMh/ptCsWcZEKcTkFhMjrRL6O9NqGmkWtjj6oqtHSV/syHvDG26mYPKM/wFjNacQh6mV12ADKk5so4A==", + "version": "1.0.0-beta.39", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-arazzo-1/-/apidom-ns-arazzo-1-1.0.0-beta.39.tgz", + "integrity": "sha512-gIiZhlt51JxEZBAZ5PfHV1c73SMQJiwJX5DnazGehMO+ojR4HyLPFh1lc6mChMxPyPlRFOfqnmx/hmNcJ/XRiA==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.0.0-beta.37", - "@swagger-api/apidom-ns-json-schema-2020-12": "^1.0.0-beta.37", + "@swagger-api/apidom-core": "^1.0.0-beta.39", + "@swagger-api/apidom-ns-json-schema-2020-12": "^1.0.0-beta.39", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -3926,15 +3926,15 @@ } }, "node_modules/@swagger-api/apidom-ns-asyncapi-2": { - "version": "1.0.0-beta.37", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-asyncapi-2/-/apidom-ns-asyncapi-2-1.0.0-beta.37.tgz", - "integrity": "sha512-I4N5VYaa4jtVYxIdsGbHe3mYmDhXvMShjhVFlIOrvmOpNE5C/AywUcN6zctlT9w8USrfMkDdc73eSvf2oNLDAw==", + "version": "1.0.0-beta.39", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-asyncapi-2/-/apidom-ns-asyncapi-2-1.0.0-beta.39.tgz", + "integrity": "sha512-Jtdo+6MgVhf8HynjRo5pIj+aYYICAQGwRkd0n0YtOhvvKoI0gWEMpcRkDbJrNcNYOHaSxMlQfotGlTCaMl7QJQ==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.0.0-beta.37", - "@swagger-api/apidom-ns-json-schema-draft-7": "^1.0.0-beta.37", + "@swagger-api/apidom-core": "^1.0.0-beta.39", + "@swagger-api/apidom-ns-json-schema-draft-7": "^1.0.0-beta.39", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -3942,15 +3942,15 @@ } }, "node_modules/@swagger-api/apidom-ns-json-schema-2019-09": { - "version": "1.0.0-beta.37", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-2019-09/-/apidom-ns-json-schema-2019-09-1.0.0-beta.37.tgz", - "integrity": "sha512-NCkbEiUILg83pl4KR4/YYsEk6QaAC4GajoFtq2/6cUktv3DgRRYx4P1A6UvOaszBQDG7PKgda3fP71CEwyd/lw==", + "version": "1.0.0-beta.39", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-2019-09/-/apidom-ns-json-schema-2019-09-1.0.0-beta.39.tgz", + "integrity": "sha512-I/XP4zbrWAmnq2KWPtbb9DKLWgzYFovIiSQOyh47bJqbYgz64/IhoZb/uGihZojVVHSqeeJH9o6JOahqHQzKOw==", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.0.0-beta.37", - "@swagger-api/apidom-error": "^1.0.0-beta.37", - "@swagger-api/apidom-ns-json-schema-draft-7": "^1.0.0-beta.37", + "@swagger-api/apidom-core": "^1.0.0-beta.39", + "@swagger-api/apidom-error": "^1.0.0-beta.39", + "@swagger-api/apidom-ns-json-schema-draft-7": "^1.0.0-beta.39", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -3958,15 +3958,15 @@ } }, "node_modules/@swagger-api/apidom-ns-json-schema-2020-12": { - "version": "1.0.0-beta.37", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-2020-12/-/apidom-ns-json-schema-2020-12-1.0.0-beta.37.tgz", - "integrity": "sha512-knPfySy8l9p1hDh2aNo8QgyLxWimeXo6SOzsw43Pv/1eoxn/9N/yCnwnRn4EcbupufFl17CsQNYS9avs2i3Few==", + "version": "1.0.0-beta.39", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-2020-12/-/apidom-ns-json-schema-2020-12-1.0.0-beta.39.tgz", + "integrity": "sha512-9bpMp96fb76lOqeggtyCU457K/XBLyw3O9fxdVS3Tevhf8P3SJ6QpabmweRb6kFt4vI3+DiBschJGn0iqmlcXw==", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.0.0-beta.37", - "@swagger-api/apidom-error": "^1.0.0-beta.37", - "@swagger-api/apidom-ns-json-schema-2019-09": "^1.0.0-beta.37", + "@swagger-api/apidom-core": "^1.0.0-beta.39", + "@swagger-api/apidom-error": "^1.0.0-beta.39", + "@swagger-api/apidom-ns-json-schema-2019-09": "^1.0.0-beta.39", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -3974,14 +3974,14 @@ } }, "node_modules/@swagger-api/apidom-ns-json-schema-draft-4": { - "version": "1.0.0-beta.37", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-4/-/apidom-ns-json-schema-draft-4-1.0.0-beta.37.tgz", - "integrity": "sha512-33AKN2yeRx25Hl/ivK/R+Ni9Ih6V7XGI28AcQs9Ej+96FegRqdMKLypHOEEF7LZZHmqd2Ioij/GZJwH+t29MTg==", + "version": "1.0.0-beta.39", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-4/-/apidom-ns-json-schema-draft-4-1.0.0-beta.39.tgz", + "integrity": "sha512-F25tm/nwPl1rRnUHzaVw4SAeASodO60oAtWX+GF3K61WEx/Aao4Maldv3CQtAoUk8L0Ml0l1KZL00sgfikwqlw==", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-ast": "^1.0.0-beta.37", - "@swagger-api/apidom-core": "^1.0.0-beta.37", + "@swagger-api/apidom-ast": "^1.0.0-beta.39", + "@swagger-api/apidom-core": "^1.0.0-beta.39", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -3989,15 +3989,15 @@ } }, "node_modules/@swagger-api/apidom-ns-json-schema-draft-6": { - "version": "1.0.0-beta.37", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-6/-/apidom-ns-json-schema-draft-6-1.0.0-beta.37.tgz", - "integrity": "sha512-d5iR8Gbnm2YrWgXmT1Hbr2I66XlumC9073O5Cf170Jj5QUbIcAGlsIFDYmG+DvWQA0pPD4xONPyoEYezagivew==", + "version": "1.0.0-beta.39", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-6/-/apidom-ns-json-schema-draft-6-1.0.0-beta.39.tgz", + "integrity": "sha512-E2fQQHWIRtbM5C1m1EL95MQNDPL98mlgYomPQDDUEFbYrH3u9BQGAgpIu4KuYasKquyuhx9YXqS/jLRhMCRfAQ==", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.0.0-beta.37", - "@swagger-api/apidom-error": "^1.0.0-beta.37", - "@swagger-api/apidom-ns-json-schema-draft-4": "^1.0.0-beta.37", + "@swagger-api/apidom-core": "^1.0.0-beta.39", + "@swagger-api/apidom-error": "^1.0.0-beta.39", + "@swagger-api/apidom-ns-json-schema-draft-4": "^1.0.0-beta.39", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -4005,15 +4005,15 @@ } }, "node_modules/@swagger-api/apidom-ns-json-schema-draft-7": { - "version": "1.0.0-beta.37", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-7/-/apidom-ns-json-schema-draft-7-1.0.0-beta.37.tgz", - "integrity": "sha512-x3k/O0x2u3WrvU1ZhIO6DQ15nVuj9tARBQdDHJnGOK5PuD6Dh04+7pLYiOrjRyukLzHcKusoTgxKd7bFNjobww==", + "version": "1.0.0-beta.39", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-7/-/apidom-ns-json-schema-draft-7-1.0.0-beta.39.tgz", + "integrity": "sha512-mhzb7n3pm0yfYuM9bZowYMp6L61Cz+HrbjBowUIt5iOMMAATQd6x209pj81hnSmgHmEJCgv+8IO9dvweme698A==", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.0.0-beta.37", - "@swagger-api/apidom-error": "^1.0.0-beta.37", - "@swagger-api/apidom-ns-json-schema-draft-6": "^1.0.0-beta.37", + "@swagger-api/apidom-core": "^1.0.0-beta.39", + "@swagger-api/apidom-error": "^1.0.0-beta.39", + "@swagger-api/apidom-ns-json-schema-draft-6": "^1.0.0-beta.39", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -4021,16 +4021,16 @@ } }, "node_modules/@swagger-api/apidom-ns-openapi-2": { - "version": "1.0.0-beta.37", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-2/-/apidom-ns-openapi-2-1.0.0-beta.37.tgz", - "integrity": "sha512-w+X20EbyMUDeRJH+k5gv0XRfZgrBNZD67OjQqwJhSDACa9DWbNryD878BMnZQVuHbZLDFdqRzJqz+UM9687/dQ==", + "version": "1.0.0-beta.39", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-2/-/apidom-ns-openapi-2-1.0.0-beta.39.tgz", + "integrity": "sha512-/Cjggp0eAM1GfKeLu53sLjCV9lFVUMucFruXJnD1TWdCKv5S5JAKsGBASbchw8hvwrfx6sPHslzZFV+tZKbn2Q==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.0.0-beta.37", - "@swagger-api/apidom-error": "^1.0.0-beta.37", - "@swagger-api/apidom-ns-json-schema-draft-4": "^1.0.0-beta.37", + "@swagger-api/apidom-core": "^1.0.0-beta.39", + "@swagger-api/apidom-error": "^1.0.0-beta.39", + "@swagger-api/apidom-ns-json-schema-draft-4": "^1.0.0-beta.39", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -4038,15 +4038,15 @@ } }, "node_modules/@swagger-api/apidom-ns-openapi-3-0": { - "version": "1.0.0-beta.37", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-0/-/apidom-ns-openapi-3-0-1.0.0-beta.37.tgz", - "integrity": "sha512-Hq1UwZ5J0XhWZU1a8XIg43xY03mKACO8u9SYIjkVwWvT5c3SK8lkf/ES3fpADExIyUIv9Z2wayIzvHTGMoEsWw==", + "version": "1.0.0-beta.39", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-0/-/apidom-ns-openapi-3-0-1.0.0-beta.39.tgz", + "integrity": "sha512-lvNlUtCmyHH8+52qOhgXXdzy4HEYA+t7xnFNvDb6dtP+epXCexux3uRs8+xEYBHo/WqUGzjdwd0qKFRgyP7Lrw==", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.0.0-beta.37", - "@swagger-api/apidom-error": "^1.0.0-beta.37", - "@swagger-api/apidom-ns-json-schema-draft-4": "^1.0.0-beta.37", + "@swagger-api/apidom-core": "^1.0.0-beta.39", + "@swagger-api/apidom-error": "^1.0.0-beta.39", + "@swagger-api/apidom-ns-json-schema-draft-4": "^1.0.0-beta.39", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -4054,17 +4054,17 @@ } }, "node_modules/@swagger-api/apidom-ns-openapi-3-1": { - "version": "1.0.0-beta.37", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-1/-/apidom-ns-openapi-3-1-1.0.0-beta.37.tgz", - "integrity": "sha512-ZhT7EvR4lVPvP541KiXtV2uGOF1/tWnVetpe4x1NLZTAXAXnWFfZVfPS2I7SPd8mavKB7Btk++Xjlv+O+82W+w==", + "version": "1.0.0-beta.39", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-1/-/apidom-ns-openapi-3-1-1.0.0-beta.39.tgz", + "integrity": "sha512-sXMJxTGL2F36Uyv9iqvPwvzsD5NJM/dJ52tUuiJP8h4RqXwjrOC86hqf1/Xk/rxgpZShhW4PNEqifvPq/Mto3w==", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-ast": "^1.0.0-beta.37", - "@swagger-api/apidom-core": "^1.0.0-beta.37", - "@swagger-api/apidom-json-pointer": "^1.0.0-beta.37", - "@swagger-api/apidom-ns-json-schema-2020-12": "^1.0.0-beta.37", - "@swagger-api/apidom-ns-openapi-3-0": "^1.0.0-beta.37", + "@swagger-api/apidom-ast": "^1.0.0-beta.39", + "@swagger-api/apidom-core": "^1.0.0-beta.39", + "@swagger-api/apidom-json-pointer": "^1.0.0-beta.39", + "@swagger-api/apidom-ns-json-schema-2020-12": "^1.0.0-beta.39", + "@swagger-api/apidom-ns-openapi-3-0": "^1.0.0-beta.39", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -4072,112 +4072,112 @@ } }, "node_modules/@swagger-api/apidom-parser-adapter-api-design-systems-json": { - "version": "1.0.0-beta.37", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-api-design-systems-json/-/apidom-parser-adapter-api-design-systems-json-1.0.0-beta.37.tgz", - "integrity": "sha512-PtDlmEY9cypb5KSrJfd2YBx0kBRxjliWmTcQ4PGSc2ZaYVnJDccsUPcnQ+rtY4s0PkYqUPVlSlxp08uurq34bA==", + "version": "1.0.0-beta.39", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-api-design-systems-json/-/apidom-parser-adapter-api-design-systems-json-1.0.0-beta.39.tgz", + "integrity": "sha512-jrPxZMvE6I2X8FUx4ri7VTMy2wTNOLLz+qXSx9sSXWULImqwdscvEwSVug8zdBQYMy8HXwt0wHpxlGLXBEmL8Q==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.0.0-beta.37", - "@swagger-api/apidom-ns-api-design-systems": "^1.0.0-beta.37", - "@swagger-api/apidom-parser-adapter-json": "^1.0.0-beta.37", + "@swagger-api/apidom-core": "^1.0.0-beta.39", + "@swagger-api/apidom-ns-api-design-systems": "^1.0.0-beta.39", + "@swagger-api/apidom-parser-adapter-json": "^1.0.0-beta.39", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-api-design-systems-yaml": { - "version": "1.0.0-beta.37", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-api-design-systems-yaml/-/apidom-parser-adapter-api-design-systems-yaml-1.0.0-beta.37.tgz", - "integrity": "sha512-z4miaZzBXCaKfZxK+boLYo6ZvpcNEx3e2+OP1Dh2JYPXYX/b26sT0jLfELbs30nOqVM+3WslfUhRi6AvQ/5XXg==", + "version": "1.0.0-beta.39", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-api-design-systems-yaml/-/apidom-parser-adapter-api-design-systems-yaml-1.0.0-beta.39.tgz", + "integrity": "sha512-JKyRYc4cBajPkIpO0YTJnxI+p8ubXfA+/1L8Fpq5kDPAI5Wh744iZ/scVHTgpgY8g+GbPqIoWB0ilQbEdlF5Sg==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.0.0-beta.37", - "@swagger-api/apidom-ns-api-design-systems": "^1.0.0-beta.37", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-beta.37", + "@swagger-api/apidom-core": "^1.0.0-beta.39", + "@swagger-api/apidom-ns-api-design-systems": "^1.0.0-beta.39", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-beta.39", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-arazzo-json-1": { - "version": "1.0.0-beta.37", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-arazzo-json-1/-/apidom-parser-adapter-arazzo-json-1-1.0.0-beta.37.tgz", - "integrity": "sha512-D8jGt31PS3pDE8j7qr+rFkHQcQUF7zHlLWRo+X0JjE6k+p03oaY0U+9A91+C5UUajS/fgDu0LqlKMXOuClMt0g==", + "version": "1.0.0-beta.39", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-arazzo-json-1/-/apidom-parser-adapter-arazzo-json-1-1.0.0-beta.39.tgz", + "integrity": "sha512-qlQuj4jsEPpLRH4wpkMjbR3Id3qb4n/oerv4cKCi1TYJJphC7DG7QRS90tYjaAF7n2YA8HxUcEIu2+Y5QZKyMw==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.0.0-beta.37", - "@swagger-api/apidom-ns-arazzo-1": "^1.0.0-beta.37", - "@swagger-api/apidom-parser-adapter-json": "^1.0.0-beta.37", + "@swagger-api/apidom-core": "^1.0.0-beta.39", + "@swagger-api/apidom-ns-arazzo-1": "^1.0.0-beta.39", + "@swagger-api/apidom-parser-adapter-json": "^1.0.0-beta.39", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-arazzo-yaml-1": { - "version": "1.0.0-beta.37", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-arazzo-yaml-1/-/apidom-parser-adapter-arazzo-yaml-1-1.0.0-beta.37.tgz", - "integrity": "sha512-nSQZt3xWOURPBlVASeXb18lFstMMTRmVf/GIT1voBdvKsmluYK3HCVNgasCTaR375xpH+NOz9KbnufN2S1UKNg==", + "version": "1.0.0-beta.39", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-arazzo-yaml-1/-/apidom-parser-adapter-arazzo-yaml-1-1.0.0-beta.39.tgz", + "integrity": "sha512-MUChnj6dJZRGDtIIVItIojzDNBEdan8KkuV+3U1l8bBA4eJQIq7yzHYk7fq6bl4Yd17HG0HT+1xckbUnj5Ay4A==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.0.0-beta.37", - "@swagger-api/apidom-ns-arazzo-1": "^1.0.0-beta.37", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-beta.37", + "@swagger-api/apidom-core": "^1.0.0-beta.39", + "@swagger-api/apidom-ns-arazzo-1": "^1.0.0-beta.39", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-beta.39", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-asyncapi-json-2": { - "version": "1.0.0-beta.37", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-json-2/-/apidom-parser-adapter-asyncapi-json-2-1.0.0-beta.37.tgz", - "integrity": "sha512-kt8WF1tNylxQAXOTJ2YfZ3QGkm8jUsQ4izqrEnxNQwI8ag3jELpBogqhnM/NSS7aGJ47BNLg7Q/Ffi2Z075AJw==", + "version": "1.0.0-beta.39", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-json-2/-/apidom-parser-adapter-asyncapi-json-2-1.0.0-beta.39.tgz", + "integrity": "sha512-G+xIeYGetnCM3ylsWSwSyqCntpT6gt2Bv3f6hu/IonZxxCy2HqUl9JS41XF/cJHCoBchJU3G+bjOZXN22W3Xzw==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.0.0-beta.37", - "@swagger-api/apidom-ns-asyncapi-2": "^1.0.0-beta.37", - "@swagger-api/apidom-parser-adapter-json": "^1.0.0-beta.37", + "@swagger-api/apidom-core": "^1.0.0-beta.39", + "@swagger-api/apidom-ns-asyncapi-2": "^1.0.0-beta.39", + "@swagger-api/apidom-parser-adapter-json": "^1.0.0-beta.39", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": { - "version": "1.0.0-beta.37", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-yaml-2/-/apidom-parser-adapter-asyncapi-yaml-2-1.0.0-beta.37.tgz", - "integrity": "sha512-F6xzrwMZGuKZ9YS09LNZby0CVhTMK8h2jDnCh8NamhGWG+BhMQpNwTv3KQfJh+YVZzE4NIN7LlsApTB4diM1qw==", + "version": "1.0.0-beta.39", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-yaml-2/-/apidom-parser-adapter-asyncapi-yaml-2-1.0.0-beta.39.tgz", + "integrity": "sha512-VPwlMRwtMQtPmEv6JXNefBBjAK/IxPsq9XWP/7kJZQ6CDp6ljHrMJDPAHZNundSL09xu7Wbz6KVGcpruUPiWmA==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.0.0-beta.37", - "@swagger-api/apidom-ns-asyncapi-2": "^1.0.0-beta.37", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-beta.37", + "@swagger-api/apidom-core": "^1.0.0-beta.39", + "@swagger-api/apidom-ns-asyncapi-2": "^1.0.0-beta.39", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-beta.39", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-json": { - "version": "1.0.0-beta.37", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-json/-/apidom-parser-adapter-json-1.0.0-beta.37.tgz", - "integrity": "sha512-bTCiQjd5IGrqnHSefyoPC9wWuWouD0CRWyEFCb7YLfXEaLp+grK3BuxImtggvqPssmDdv5XsioVhKquZE5Hvzw==", + "version": "1.0.0-beta.39", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-json/-/apidom-parser-adapter-json-1.0.0-beta.39.tgz", + "integrity": "sha512-Rg2SgI06CDTdm3qs3A5pNvhonVxa2jOcwypxyhKngelIHaTuOPgaFA6qyCIvX0oIhwTcKcvV+5tPlGIR3vshpA==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-ast": "^1.0.0-beta.37", - "@swagger-api/apidom-core": "^1.0.0-beta.37", - "@swagger-api/apidom-error": "^1.0.0-beta.37", + "@swagger-api/apidom-ast": "^1.0.0-beta.39", + "@swagger-api/apidom-core": "^1.0.0-beta.39", + "@swagger-api/apidom-error": "^1.0.0-beta.39", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", @@ -4199,112 +4199,112 @@ } }, "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-2": { - "version": "1.0.0-beta.37", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-2/-/apidom-parser-adapter-openapi-json-2-1.0.0-beta.37.tgz", - "integrity": "sha512-68gnZwSAC1eDAEcEQJSFlcJMEJBNkyHN1hWsfml+STwcnSxUOAe65A6kH2r970ekHzn4Ip8syk5WOPU46Q1epg==", + "version": "1.0.0-beta.39", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-2/-/apidom-parser-adapter-openapi-json-2-1.0.0-beta.39.tgz", + "integrity": "sha512-DYB3jGcSnTu1ykbAKkMo550QW0BjnHlGxi1NaBbVYzdMfPYbBSQnDB3zYAwgakpoQx89OztFSlrNQ+3P8wuuSw==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.0.0-beta.37", - "@swagger-api/apidom-ns-openapi-2": "^1.0.0-beta.37", - "@swagger-api/apidom-parser-adapter-json": "^1.0.0-beta.37", + "@swagger-api/apidom-core": "^1.0.0-beta.39", + "@swagger-api/apidom-ns-openapi-2": "^1.0.0-beta.39", + "@swagger-api/apidom-parser-adapter-json": "^1.0.0-beta.39", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-3-0": { - "version": "1.0.0-beta.37", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-0/-/apidom-parser-adapter-openapi-json-3-0-1.0.0-beta.37.tgz", - "integrity": "sha512-tTr43YkXnHhZMX6tbeH4SP2F6roK1b7BN0iZMCBUhD+gQiu4ZfhQBz8kJWkfTHtO1JB8sZoXUDQzY4PeITF25A==", + "version": "1.0.0-beta.39", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-0/-/apidom-parser-adapter-openapi-json-3-0-1.0.0-beta.39.tgz", + "integrity": "sha512-4ueWFNc3N4YZb7fTwsgrhWzdCo3TnZ7HgK5fPW3m0+Hm2wko2WYIUCIxU33Ef4DB+0Hd8y4Abjv8Mz0CCxRaeg==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.0.0-beta.37", - "@swagger-api/apidom-ns-openapi-3-0": "^1.0.0-beta.37", - "@swagger-api/apidom-parser-adapter-json": "^1.0.0-beta.37", + "@swagger-api/apidom-core": "^1.0.0-beta.39", + "@swagger-api/apidom-ns-openapi-3-0": "^1.0.0-beta.39", + "@swagger-api/apidom-parser-adapter-json": "^1.0.0-beta.39", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-3-1": { - "version": "1.0.0-beta.37", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-1/-/apidom-parser-adapter-openapi-json-3-1-1.0.0-beta.37.tgz", - "integrity": "sha512-UGyVyh6ppl9SrPjDhbzRYzRGOClf+PisEWU2bdkNp5Ak4narqSppCCL65iGF/flasCDhODidBHJuJUY3Rx2rdg==", + "version": "1.0.0-beta.39", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-1/-/apidom-parser-adapter-openapi-json-3-1-1.0.0-beta.39.tgz", + "integrity": "sha512-8K/9J1CnQAwVqN+pvP9PH607WKA7WimNmZiyczgfnOgq93PUozNavrK97lwUUOQcZUmQra8pG2XrOrZZwd/M5w==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.0.0-beta.37", - "@swagger-api/apidom-ns-openapi-3-1": "^1.0.0-beta.37", - "@swagger-api/apidom-parser-adapter-json": "^1.0.0-beta.37", + "@swagger-api/apidom-core": "^1.0.0-beta.39", + "@swagger-api/apidom-ns-openapi-3-1": "^1.0.0-beta.39", + "@swagger-api/apidom-parser-adapter-json": "^1.0.0-beta.39", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-2": { - "version": "1.0.0-beta.37", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-2/-/apidom-parser-adapter-openapi-yaml-2-1.0.0-beta.37.tgz", - "integrity": "sha512-q6sqIfvXwq9VlwaXYznsaNYSj4MtAZhQGPvb7sWJvtPM/mi14uV08f3ZYCxjrDQg1kuElvEiXztcJPvnpfwL0A==", + "version": "1.0.0-beta.39", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-2/-/apidom-parser-adapter-openapi-yaml-2-1.0.0-beta.39.tgz", + "integrity": "sha512-EfzVZrYTnwNGXUXIOrZkigQxdze+VdXxJWp55t3CWTy0CA7w9eM+PDpzHu7iqJgXqTOixMGy02Gzyv6N6sDj8A==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.0.0-beta.37", - "@swagger-api/apidom-ns-openapi-2": "^1.0.0-beta.37", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-beta.37", + "@swagger-api/apidom-core": "^1.0.0-beta.39", + "@swagger-api/apidom-ns-openapi-2": "^1.0.0-beta.39", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-beta.39", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": { - "version": "1.0.0-beta.37", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-0/-/apidom-parser-adapter-openapi-yaml-3-0-1.0.0-beta.37.tgz", - "integrity": "sha512-CEdFueD/QKaWEp3d259Q7QkmqgxjYoaj1uj4tgm+9yErjIJQWUJWQmPMxDaGROglYYDfBw/qEwZJbKHAzze/Kw==", + "version": "1.0.0-beta.39", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-0/-/apidom-parser-adapter-openapi-yaml-3-0-1.0.0-beta.39.tgz", + "integrity": "sha512-ElHueuGdwB35VeZaJnmhZE3ILGE8F74ThJqgTbY+F2JcNo4O8cBkoCq9syw1pJ+l2JoAUErmxaTOR+zNA/wK+g==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.0.0-beta.37", - "@swagger-api/apidom-ns-openapi-3-0": "^1.0.0-beta.37", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-beta.37", + "@swagger-api/apidom-core": "^1.0.0-beta.39", + "@swagger-api/apidom-ns-openapi-3-0": "^1.0.0-beta.39", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-beta.39", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": { - "version": "1.0.0-beta.37", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-1/-/apidom-parser-adapter-openapi-yaml-3-1-1.0.0-beta.37.tgz", - "integrity": "sha512-+bCYbidJaSqTkhVkDZZfTaMqc9F11XvSWFUjodXV0K9jRuDMKKY09uuOARVkUnEFZssUp7GSBpjaoXmjVJA+Dg==", + "version": "1.0.0-beta.39", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-1/-/apidom-parser-adapter-openapi-yaml-3-1-1.0.0-beta.39.tgz", + "integrity": "sha512-lNSXp+vGcsA/d/3ukXJeovAnO5oxcTJ5OvFBL84grJvK1C6E2v0AOfsMlUEipIRNhIHq3zYKpUnhFJyE13VqXA==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.0.0-beta.37", - "@swagger-api/apidom-ns-openapi-3-1": "^1.0.0-beta.37", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-beta.37", + "@swagger-api/apidom-core": "^1.0.0-beta.39", + "@swagger-api/apidom-ns-openapi-3-1": "^1.0.0-beta.39", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-beta.39", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0" } }, "node_modules/@swagger-api/apidom-parser-adapter-yaml-1-2": { - "version": "1.0.0-beta.37", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-yaml-1-2/-/apidom-parser-adapter-yaml-1-2-1.0.0-beta.37.tgz", - "integrity": "sha512-aO7XosQpEBIEOVRXE/Um3sW2P/ZZLGgLYJhwHY2BcmPsflslGT3vqvZVXvFDHoqrfb3BDZY67IcbzMW04koE3Q==", + "version": "1.0.0-beta.39", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-yaml-1-2/-/apidom-parser-adapter-yaml-1-2-1.0.0-beta.39.tgz", + "integrity": "sha512-30Lhgkg2ZrHY7tQ5h9umjWWhy0Fqcoi28SXJ9vtdj1cLSnFvclaLe5ZGbXP3wdW4sXZO0As3+msL9tMwrUJ/7w==", "license": "Apache-2.0", "optional": true, "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-ast": "^1.0.0-beta.37", - "@swagger-api/apidom-core": "^1.0.0-beta.37", - "@swagger-api/apidom-error": "^1.0.0-beta.37", + "@swagger-api/apidom-ast": "^1.0.0-beta.39", + "@swagger-api/apidom-core": "^1.0.0-beta.39", + "@swagger-api/apidom-error": "^1.0.0-beta.39", "@tree-sitter-grammars/tree-sitter-yaml": "=0.7.0", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", @@ -4346,16 +4346,16 @@ } }, "node_modules/@swagger-api/apidom-reference": { - "version": "1.0.0-beta.37", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-reference/-/apidom-reference-1.0.0-beta.37.tgz", - "integrity": "sha512-5rvC7oTgkcOGKPpBh3fskKV/meE9mSP8iaAP5b+HHoT72O24Me7AkVs0wiLthM3/NggEZDKjwfwG491fQHxotg==", + "version": "1.0.0-beta.39", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-reference/-/apidom-reference-1.0.0-beta.39.tgz", + "integrity": "sha512-PrV2/3Z6XGJPj4fv1JazY1dKjlnAg/BN22UQdUOzA5/A0TkfbImt8uVQuVzQSL2P8RA6G9TDsdpOalj80N47rw==", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.26.10", - "@swagger-api/apidom-core": "^1.0.0-beta.37", - "@swagger-api/apidom-error": "^1.0.0-beta.37", + "@swagger-api/apidom-core": "^1.0.0-beta.39", + "@swagger-api/apidom-error": "^1.0.0-beta.39", "@types/ramda": "~0.30.0", - "axios": "^1.8.2", + "axios": "^1.9.0", "minimatch": "^7.4.3", "process": "^0.11.10", "ramda": "~0.30.0", @@ -11133,9 +11133,9 @@ "license": "BSD-2-Clause" }, "node_modules/short-unique-id": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/short-unique-id/-/short-unique-id-5.2.0.tgz", - "integrity": "sha512-cMGfwNyfDZ/nzJ2k2M+ClthBIh//GlZl1JEf47Uoa9XR11bz8Pa2T2wQO4bVrRdH48LrIDWJahQziKo3MjhsWg==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/short-unique-id/-/short-unique-id-5.3.2.tgz", + "integrity": "sha512-KRT/hufMSxXKEDSQujfVE0Faa/kZ51ihUcZQAcmP04t00DvPj7Ox5anHke1sJYUtzSuiT/Y5uyzg/W7bBEGhCg==", "license": "Apache-2.0", "bin": { "short-unique-id": "bin/short-unique-id", @@ -11640,18 +11640,18 @@ } }, "node_modules/swagger-client": { - "version": "3.35.0", - "resolved": "https://registry.npmjs.org/swagger-client/-/swagger-client-3.35.0.tgz", - "integrity": "sha512-AOs1GV0ucu7rNluT0tq0kSslEBvPhgIznwZnqs0fl+98MbpV4NtzbnHypRG1I93sS79Jj2bPtqhzujtnSS049w==", + "version": "3.35.3", + "resolved": "https://registry.npmjs.org/swagger-client/-/swagger-client-3.35.3.tgz", + "integrity": "sha512-4bO+dhBbasP485Ak67o46cWNVUnV0/92ypb2997bhvxTO2M+IuQZM1ilkN/7nSaiGuxDKJhkuL54I35PVI3AAw==", "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.22.15", "@scarf/scarf": "=1.4.0", - "@swagger-api/apidom-core": ">=1.0.0-beta.31 <1.0.0-rc.0", - "@swagger-api/apidom-error": ">=1.0.0-beta.31 <1.0.0-rc.0", - "@swagger-api/apidom-json-pointer": ">=1.0.0-beta.31 <1.0.0-rc.0", - "@swagger-api/apidom-ns-openapi-3-1": ">=1.0.0-beta.31 <1.0.0-rc.0", - "@swagger-api/apidom-reference": ">=1.0.0-beta.31 <1.0.0-rc.0", + "@swagger-api/apidom-core": ">=1.0.0-beta.39 <1.0.0-rc.0", + "@swagger-api/apidom-error": ">=1.0.0-beta.39 <1.0.0-rc.0", + "@swagger-api/apidom-json-pointer": ">=1.0.0-beta.39 <1.0.0-rc.0", + "@swagger-api/apidom-ns-openapi-3-1": ">=1.0.0-beta.39 <1.0.0-rc.0", + "@swagger-api/apidom-reference": ">=1.0.0-beta.39 <1.0.0-rc.0", "@swaggerexpert/cookie": "^2.0.2", "deepmerge": "~4.3.0", "fast-json-patch": "^3.0.0-1", @@ -11728,12 +11728,12 @@ } }, "node_modules/swagger-ui": { - "version": "5.21.0", - "resolved": "https://registry.npmjs.org/swagger-ui/-/swagger-ui-5.21.0.tgz", - "integrity": "sha512-zAY5P5nIWiYOuO0SWQk1x8/kL+pmarijO+oviWOp+SerfMpeokujYk6HknwEoeYNi4CtpO+kBj6Vm+8aswCBIA==", + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/swagger-ui/-/swagger-ui-5.22.0.tgz", + "integrity": "sha512-xGc24TU6wgS5YWVcxwy90GUgqbHYvWvtvftjptfjXEMgetdvGWnI8CNRaYG3hG5bIl5Zt1rfai4NO2lBxAUswg==", "license": "Apache-2.0", "dependencies": { - "@babel/runtime-corejs3": "^7.26.10", + "@babel/runtime-corejs3": "^7.27.1", "@scarf/scarf": "=1.4.0", "base64-js": "^1.5.1", "classnames": "^2.5.1", @@ -11763,7 +11763,7 @@ "reselect": "^5.1.1", "serialize-error": "^8.1.0", "sha.js": "^2.4.11", - "swagger-client": "^3.34.4", + "swagger-client": "^3.35.3", "url-parse": "^1.5.10", "xml": "=1.0.1", "xml-but-prettier": "^1.0.1", From 1c896690ef09ffa7c86a9b846ece6be758ac1af4 Mon Sep 17 00:00:00 2001 From: Diego Alfaro Pinto Date: Tue, 27 May 2025 11:59:31 -0600 Subject: [PATCH 023/116] feat: agregar crear set de empleados --- .../crearSetsProductos.controller.js | 23 ++++++++ .../repositorioCrearSetsProductos.js | 30 ++++++++++ .../crearSetsProductos.routes.js | 16 +++++ .../Rutas/indexSetsProductos.routes.js | 3 + .../Constantes/consultasSetsProductos.js | 58 ++++++++++++------- .../Constantes/mensajesSetsProductos.js | 16 +++++ 6 files changed, 125 insertions(+), 21 deletions(-) create mode 100644 SetsProductos/Controladores/crearSetsProductos.controller.js create mode 100644 SetsProductos/Datos/Repositorios/repositorioCrearSetsProductos.js create mode 100644 SetsProductos/Rutas/RutasIndividuales/crearSetsProductos.routes.js diff --git a/SetsProductos/Controladores/crearSetsProductos.controller.js b/SetsProductos/Controladores/crearSetsProductos.controller.js new file mode 100644 index 00000000..7fba9d13 --- /dev/null +++ b/SetsProductos/Controladores/crearSetsProductos.controller.js @@ -0,0 +1,23 @@ +const MENSAJES_SETS_PRODUCTOS = require('@altertex/util/const/mensajesSetsProductos'); +const repositorio = require('@altertex/setspro/repos/repositorioCrearSetsProductos'); +exports.crearSetsProductos = async (req, res) => { + const datos = req.body; + const cliente = req.user.clienteSeleccionado; + + if (!datos.nombre || !datos.nombreVisible || !datos.descripcion || !datos.activo || !datos.idProductos) { + return res.status(MENSAJES_SETS_PRODUCTOS.DATOS_INVALIDOS_ERROR.codigo).json({ mensaje: MENSAJES_SETS_PRODUCTOS.DATOS_INVALIDOS_ERROR.mensaje }); + } + + if (!cliente) { + return res.status(MENSAJES_SETS_PRODUCTOS.CLIENTE_NO_SELECCIONADO.codigo).json({ mensaje: MENSAJES_SETS_PRODUCTOS.CLIENTE_NO_SELECCIONADO.mensaje }); + } + + try { + await repositorio.crearSetsProductos(cliente, datos); + return res.status(MENSAJES_SETS_PRODUCTOS.SETS_PRODUCTOS_CREADO_EXITO.codigo).json({ mensaje: MENSAJES_SETS_PRODUCTOS.SETS_PRODUCTOS_CREADO_EXITO.mensaje }); + } catch (error) { + return res.status(500).json({ mensaje: error.message }); + } + + +}; \ No newline at end of file diff --git a/SetsProductos/Datos/Repositorios/repositorioCrearSetsProductos.js b/SetsProductos/Datos/Repositorios/repositorioCrearSetsProductos.js new file mode 100644 index 00000000..0b08f958 --- /dev/null +++ b/SetsProductos/Datos/Repositorios/repositorioCrearSetsProductos.js @@ -0,0 +1,30 @@ +const MENSAJES_SETS_PRODUCTOS = require('@altertex/util/const/mensajesSetsProductos'); +const correrQuery = require('@altertex/util/ser/correrQuery'); +const CONSULTAS = require('@altertex/util/const/consultasSetsProductos'); + +exports.crearSetsProductos = async (idCliente, datosSetsProducto) => { + try { + + const duplicados = await correrQuery(CONSULTAS.CONSULTAR_DUPLICADOS, [ + idCliente, + datosSetsProducto.nombre, + datosSetsProducto.nombreVisible, + ]); + + if (duplicados.length > 0) { + throw new Error(MENSAJES_SETS_PRODUCTOS.ERROR_NOMBRE_DUPLICADO.mensaje); + } + + + const resultado = await correrQuery(CONSULTAS.CREAR_SET_PRODUCTO, [idCliente, datosSetsProducto.nombre, datosSetsProducto.nombreVisible, datosSetsProducto.descripcion, datosSetsProducto.activo]); + const idSetProducto = resultado.insertId; + + + for (const producto of datosSetsProducto.idProductos) { + await correrQuery(CONSULTAS.ASIGNAR_PRODUCTO_SET_PRODUCTO, [producto, idSetProducto]); + } + } catch (error) { + throw new Error(error.message); + } + +}; \ No newline at end of file diff --git a/SetsProductos/Rutas/RutasIndividuales/crearSetsProductos.routes.js b/SetsProductos/Rutas/RutasIndividuales/crearSetsProductos.routes.js new file mode 100644 index 00000000..40001fa1 --- /dev/null +++ b/SetsProductos/Rutas/RutasIndividuales/crearSetsProductos.routes.js @@ -0,0 +1,16 @@ +const express = require('express'); +const ruteador = express.Router(); +const revisarApiKey = require('@altertex/util/inter/revisarApiKey'); +const autorizarToken = require('@altertex/util/inter/autorizarToken'); +const verificarPermisos = require('@altertex/util/inter/verificarPermisos'); +const limitePeticionesDiarias = require('@altertex/util/inter/limitePeticiones'); +const validarYSanitizar = require('@altertex/util/inter/validarYSanitizar'); + +const controlador = require('@altertex/setspro/ctrl/crearSetsProductos.controller'); + +const PERMISOS = require('@altertex/util/const/permisos'); +const RUTAS = require('@altertex/util/const/rutas'); + +ruteador.post(RUTAS.SETS_PRODUCTOS.CREAR, validarYSanitizar, revisarApiKey(), autorizarToken, limitePeticionesDiarias, verificarPermisos(PERMISOS.CREAR_SET_PRODUCTOS), controlador.crearSetsProductos); + +module.exports = ruteador; \ No newline at end of file diff --git a/SetsProductos/Rutas/indexSetsProductos.routes.js b/SetsProductos/Rutas/indexSetsProductos.routes.js index a30971e5..d57927a9 100644 --- a/SetsProductos/Rutas/indexSetsProductos.routes.js +++ b/SetsProductos/Rutas/indexSetsProductos.routes.js @@ -2,6 +2,7 @@ const express = require('express'); const ruteador = express.Router(); const rutasConsultarSetsProductos = require('@altertex/setspro/rutasInd/consultarSetsProductos.routes'); const rutasEliminarSetsProductos = require('@altertex/setspro/rutasInd/eliminarSetsProductos.routes'); +const rutasCrearSetsProductos = require('@altertex/setspro/rutasInd/crearSetsProductos.routes'); const RUTAS = require('@altertex/util/const/rutas'); @@ -11,4 +12,6 @@ ruteador.use(RUTAS.SETS_PRODUCTOS.BASE, rutasConsultarSetsProductos); //RF[45] Elimina set de productos - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF45] ruteador.use(RUTAS.SETS_PRODUCTOS.BASE, rutasEliminarSetsProductos); +ruteador.use(RUTAS.SETS_PRODUCTOS.BASE, rutasCrearSetsProductos); + module.exports = ruteador; diff --git a/Utilidades/Constantes/consultasSetsProductos.js b/Utilidades/Constantes/consultasSetsProductos.js index 741e9a16..f2239349 100644 --- a/Utilidades/Constantes/consultasSetsProductos.js +++ b/Utilidades/Constantes/consultasSetsProductos.js @@ -1,30 +1,46 @@ module.exports = { OBTENER_LISTA: ` - SELECT - sp.idSetProducto, - sp.nombre, - sp.descripcion, - sp.activo, - GROUP_CONCAT(DISTINCT p.nombreComun SEPARATOR ', ') AS productos, - GROUP_CONCAT(DISTINCT ge.nombre SEPARATOR ', ') AS grupos - FROM set_producto sp - LEFT JOIN producto_set_producto psp ON psp.idSetProducto = sp.idSetProducto - LEFT JOIN producto p ON p.idProducto = psp.idProducto - LEFT JOIN set_producto_grupo_empleado spge ON spge.idSetProducto = sp.idSetProducto - LEFT JOIN grupo_empleado ge ON ge.idGrupo = spge.idGrupo - WHERE sp.idCliente = ? - GROUP BY sp.idSetProducto, sp.nombre, sp.descripcion, sp.activo; - `, + SELECT sp.idSetProducto, + sp.nombre, + sp.descripcion, + sp.activo, + GROUP_CONCAT(DISTINCT p.nombreComun SEPARATOR ', ') AS productos, + GROUP_CONCAT(DISTINCT ge.nombre SEPARATOR ', ') AS grupos + FROM set_producto sp + LEFT JOIN producto_set_producto psp ON psp.idSetProducto = sp.idSetProducto + LEFT JOIN producto p ON p.idProducto = psp.idProducto + LEFT JOIN set_producto_grupo_empleado spge ON spge.idSetProducto = sp.idSetProducto + LEFT JOIN grupo_empleado ge ON ge.idGrupo = spge.idGrupo + WHERE sp.idCliente = ? + GROUP BY sp.idSetProducto, sp.nombre, sp.descripcion, sp.activo; + `, ELIMINAR_SET_PRODUCTOS_GRUPO_EMPLEADOS: ` - DELETE FROM set_producto_grupo_empleado + DELETE + FROM set_producto_grupo_empleado WHERE idSetProducto = ?; - `, + `, ELIMINAR_PRODUCTOS_SET_PRODUCTOS: ` - DELETE FROM producto_set_producto + DELETE + FROM producto_set_producto WHERE idSetProducto = ?; - `, + `, ELIMINAR_SET_PRODUCTOS: ` - DELETE FROM set_producto + DELETE + FROM set_producto WHERE idSetProducto = ?; - `, + `, + CREAR_SET_PRODUCTO: ` + INSERT INTO set_producto (idCliente, nombre, nombreVisible, descripcion, activo) + VALUES (?, ?, ?, ?, ?); + `, + ASIGNAR_PRODUCTO_SET_PRODUCTO: ` + INSERT INTO producto_set_producto (idProducto, idSetProducto) + VALUES (?, ?); + `, + CONSULTAR_DUPLICADOS: ` + SELECT idSetProducto + FROM set_producto + WHERE idCliente = ? + AND (nombre = ? OR nombreVisible = ?); + `, }; diff --git a/Utilidades/Constantes/mensajesSetsProductos.js b/Utilidades/Constantes/mensajesSetsProductos.js index f887f58c..ccfe0a2c 100644 --- a/Utilidades/Constantes/mensajesSetsProductos.js +++ b/Utilidades/Constantes/mensajesSetsProductos.js @@ -36,4 +36,20 @@ module.exports = { codigo: 500, mensaje: 'Ocurrió un error al eliminar el set de productos.', }, + DATOS_INVALIDOS_ERROR: { + codigo: 400, + mensaje: 'Formato de datos invalido.', + }, + CLIENTE_NO_SELECCIONADO: { + codigo: 400, + mensaje: 'Cliente no seleccionado.', + }, + SETS_PRODUCTOS_CREADO_EXITO: { + codigo: 200, + mensaje: 'Set de producto creado exitosamente.', + }, + ERROR_NOMBRE_DUPLICADO: { + codigo: 400, + mensaje: 'Nombre o nombre visible duplicado.', + }, }; From 3d3e99740e1e058f87118a14c8736d52833c9e86 Mon Sep 17 00:00:00 2001 From: Diego Alfaro Pinto Date: Tue, 27 May 2025 12:37:04 -0600 Subject: [PATCH 024/116] feat: agregar validacion de productos --- .../Repositorios/repositorioCrearSetsProductos.js | 10 +++++++++- Utilidades/Constantes/consultasSetsProductos.js | 5 +++++ Utilidades/Constantes/mensajesSetsProductos.js | 4 ++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/SetsProductos/Datos/Repositorios/repositorioCrearSetsProductos.js b/SetsProductos/Datos/Repositorios/repositorioCrearSetsProductos.js index 0b08f958..6d9e4a6e 100644 --- a/SetsProductos/Datos/Repositorios/repositorioCrearSetsProductos.js +++ b/SetsProductos/Datos/Repositorios/repositorioCrearSetsProductos.js @@ -14,7 +14,15 @@ exports.crearSetsProductos = async (idCliente, datosSetsProducto) => { if (duplicados.length > 0) { throw new Error(MENSAJES_SETS_PRODUCTOS.ERROR_NOMBRE_DUPLICADO.mensaje); } - + + const ids = datosSetsProducto.idProductos; + const temporal = ids.map(() => '?').join(', '); + const queryProductos = CONSULTAS.CONSULTAR_PRODUCTOS_EXISTENTES.replace('__IDS__', temporal); + const productosExistentes = await correrQuery(queryProductos, ids); + + if (productosExistentes.length !== ids.length) { + throw new Error(MENSAJES_SETS_PRODUCTOS.ERROR_PRODUCTOS_INVALIDOS.mensaje); + } const resultado = await correrQuery(CONSULTAS.CREAR_SET_PRODUCTO, [idCliente, datosSetsProducto.nombre, datosSetsProducto.nombreVisible, datosSetsProducto.descripcion, datosSetsProducto.activo]); const idSetProducto = resultado.insertId; diff --git a/Utilidades/Constantes/consultasSetsProductos.js b/Utilidades/Constantes/consultasSetsProductos.js index f2239349..83f02475 100644 --- a/Utilidades/Constantes/consultasSetsProductos.js +++ b/Utilidades/Constantes/consultasSetsProductos.js @@ -43,4 +43,9 @@ module.exports = { WHERE idCliente = ? AND (nombre = ? OR nombreVisible = ?); `, + CONSULTAR_PRODUCTOS_EXISTENTES: ` + SELECT idProducto + FROM producto + WHERE idProducto IN (__IDS__); + `, }; diff --git a/Utilidades/Constantes/mensajesSetsProductos.js b/Utilidades/Constantes/mensajesSetsProductos.js index ccfe0a2c..48f3fcfb 100644 --- a/Utilidades/Constantes/mensajesSetsProductos.js +++ b/Utilidades/Constantes/mensajesSetsProductos.js @@ -52,4 +52,8 @@ module.exports = { codigo: 400, mensaje: 'Nombre o nombre visible duplicado.', }, + ERROR_PRODUCTOS_INVALIDOS: { + codigo: 400, + mensaje: 'Uno o más productos no existen en este cliente.', + }, }; From 977ec02f47f61713891b960c0cd349ae7547856a Mon Sep 17 00:00:00 2001 From: PAOLA MARIA garrido Date: Tue, 27 May 2025 14:17:02 -0600 Subject: [PATCH 025/116] feat(RF59): agregar controlador, rutas y repositorio para exportar empleados --- .../exportarEmpleados.controller.js | 58 +++++++++++ .../repositorioExportarEmpleado.js | 18 ++++ .../exportarEmpleados.routes.js | 97 +++++++++++++++++++ Empleados/Rutas/indexEmpleados.routes.js | 7 +- Utilidades/Constantes/mensajesEmpleados.js | 14 ++- Utilidades/Constantes/rutas.js | 1 + 6 files changed, 192 insertions(+), 3 deletions(-) create mode 100644 Empleados/Controladores/exportarEmpleados.controller.js create mode 100644 Empleados/Datos/Repositorios/repositorioExportarEmpleado.js create mode 100644 Empleados/Rutas/RutasIndividuales/exportarEmpleados.routes.js diff --git a/Empleados/Controladores/exportarEmpleados.controller.js b/Empleados/Controladores/exportarEmpleados.controller.js new file mode 100644 index 00000000..9b544e1f --- /dev/null +++ b/Empleados/Controladores/exportarEmpleados.controller.js @@ -0,0 +1,58 @@ +const repositorio = require('@altertex/emp/repos/repositorioExportarEmpleado'); +const { Parser } = require('json2csv'); +const MENSAJES_EMPLEADOS = require('@altertex/util/const/mensajesEmpleados'); + +/** + * Controlador para exportar empleados y retornar CSV como string en JSON. + * + * + * + * @async + * @function exportarEmpleados + * @param {Request} req + * @param {Response} res + * @returns {Response} JSON con mensaje + contenido CSV en texto plano + * @see [RF59 - Documentación de requisitos](https://codeandco-wiki.netlify.app/docs/next/proyectos/textiles/documentacion/requisitos/RF59) + */ +exports.exportarEmpleados = async (req, res) => { + try { + const empleados = await repositorio.obtenerEmpleadosExportacion(); + + if (!empleados || empleados.length === 0) { + return res.status(MENSAJES_EMPLEADOS.EMPLEADOS_NO_ENCONTRADOS.codigo).json({ + mensaje: MENSAJES_EMPLEADOS.EMPLEADOS_NO_ENCONTRADOS.mensaje, + csv: '' + }); + } + + const campos = [ + { label: 'ID', value: 'idEmpleado' }, + { label: 'Nombre completo', value: 'nombreCompleto' }, + { label: 'Correo electrónico', value: 'correoElectronico' }, + { label: 'Número de teléfono', value: 'numeroTelefono' }, + { label: 'Dirección', value: 'direccion' }, + { label: 'Fecha de nacimiento', value: 'fechaNacimiento' }, + { label: 'Género', value: 'genero' }, + { label: 'Estatus', value: 'estatus' }, + { label: 'Número de emergencia', value: 'numeroEmergencia' }, + { label: 'Área de trabajo', value: 'areaTrabajo' }, + { label: 'Posición', value: 'posicion' }, + { label: 'Cantidad de puntos', value: 'cantidadPuntos' }, + { label: 'Antigüedad', value: 'antiguedad' } + ]; + + const parser = new Parser({ fields: campos }); + const csv = parser.parse(empleados); + + return res.status(MENSAJES_EMPLEADOS.LISTA_EMPLEADOS_EXPORTADA.codigo).json({ + mensaje: MENSAJES_EMPLEADOS.LISTA_EMPLEADOS_EXPORTADA.mensaje, + csv // string con contenido CSV para que el frontend lo descargue + }); + } catch (error) { + console.error('Error al exportar empleados:', error); + return res.status(MENSAJES_EMPLEADOS.ERROR_EXPORTAR_EMPLEADOS.codigo).json({ + mensaje: MENSAJES_EMPLEADOS.ERROR_EXPORTAR_EMPLEADOS.mensaje, + csv: '' + }); + } +}; \ No newline at end of file diff --git a/Empleados/Datos/Repositorios/repositorioExportarEmpleado.js b/Empleados/Datos/Repositorios/repositorioExportarEmpleado.js new file mode 100644 index 00000000..6a4d2dbe --- /dev/null +++ b/Empleados/Datos/Repositorios/repositorioExportarEmpleado.js @@ -0,0 +1,18 @@ +const correrQuery = require('@altertex/util/ser/correrQuery'); +const CONSULTAS_EXPORTAR_EMPLEADOS = require('@altertex/util/const/consultasExportarEmpleados'); + +/** + * Consulta la lista de empleados con todos los datos necesarios para exportar en CSV. + * + * @function + * @async + * @returns {Promise>} Arreglo de empleados con sus datos combinados (usuario + empleado). + * + * @throws {Error} Lanza un error si ocurre un fallo al ejecutar la consulta a la base de datos. + * + * @see [RF59 - Documentación de requisitos](https://codeandco-wiki.netlify.app/docs/next/proyectos/textiles/documentacion/requisitos/RF59) + */ +exports.obtenerEmpleadosExportacion = () => { + const query = CONSULTAS_EXPORTAR_EMPLEADOS.OBTENER_DATOS_EXPORTACION; + return correrQuery(query); +}; diff --git a/Empleados/Rutas/RutasIndividuales/exportarEmpleados.routes.js b/Empleados/Rutas/RutasIndividuales/exportarEmpleados.routes.js new file mode 100644 index 00000000..faa3b8a4 --- /dev/null +++ b/Empleados/Rutas/RutasIndividuales/exportarEmpleados.routes.js @@ -0,0 +1,97 @@ +//RF59 - Exportar Empleados- https://codeandco-wiki.netlify.app/docs/next/proyectos/textiles/documentacion/requisitos/RF59 + +const express = require('express'); +const ruteador = express.Router(); +const controlador = require('@altertex/emp/ctrl/exportarEmpleados.controller'); +const revisarApiKey = require('@altertex/util/inter/revisarApiKey'); +const autorizarToken = require('@altertex/util/inter/autorizarToken'); +const verificarPermisos = require('@altertex/util/inter/verificarPermisos'); +const PERMISOS = require('@altertex/util/const/permisos'); +const RUTAS = require('@altertex/util/const/rutas'); +const validarYSanitizar = require('@altertex/util/inter/validarYSanitizar'); +const limitePeticionesDiarias = require('@altertex/util/inter/limitePeticiones'); + +/** + * @swagger + * /api/empleados/exportar: + * get: + * summary: Exporta la lista completa de empleados en formato CSV. + * tags: + * - Empleados + * security: + * - ApiKeyAuth: [] + * - BearerAuth: [] + * parameters: + * - in: header + * name: x-api-key + * required: true + * schema: + * type: string + * description: Clave de API + * - in: header + * name: Authorization + * required: true + * schema: + * type: string + * description: Token JWT con formato "Bearer " + * responses: + * 200: + * description: Archivo CSV generado correctamente. + * content: + * text/csv: + * schema: + * type: string + * format: binary + * 204: + * description: No hay empleados para exportar. + * content: + * application/json: + * schema: + * type: object + * properties: + * mensaje: + * type: string + * example: No hay empleados para exportar. + * 400: + * description: Clave de API inválida o ausente. + * content: + * application/json: + * schema: + * type: object + * properties: + * mensaje: + * type: string + * example: API key inválida. + * 401: + * description: Usuario no autenticado o token JWT inválido. + * content: + * application/json: + * schema: + * type: object + * properties: + * mensaje: + * type: string + * example: No autorizado. + * 500: + * description: Error interno del servidor al exportar empleados. + * content: + * application/json: + * schema: + * type: object + * properties: + * mensaje: + * type: string + * example: Error al exportar la lista de empleados. + */ + +ruteador.get( + RUTAS.EMPLEADOS.EXPORTAR_EMPLEADOS, + revisarApiKey(), + autorizarToken, + limitePeticionesDiarias, + validarYSanitizar, + verificarPermisos(PERMISOS.EXPORTAR_EMPLEADOS), + controlador.exportarEmpleados +); + +module.exports = ruteador; \ No newline at end of file diff --git a/Empleados/Rutas/indexEmpleados.routes.js b/Empleados/Rutas/indexEmpleados.routes.js index f9ff9fe4..8f85c02c 100644 --- a/Empleados/Rutas/indexEmpleados.routes.js +++ b/Empleados/Rutas/indexEmpleados.routes.js @@ -5,6 +5,7 @@ const rutasConsultarLista = require('@altertex/emp/rutasInd/consultarLista.route const rutasEliminarGrupo = require('@altertex/emp/rutasInd/eliminarGrupoEmpleados.routes'); const rutasEliminarEmpleado = require('@altertex/emp/rutasInd/eliminarEmpleado.routes'); const rutasImportarEmpleados = require('@altertex/emp/rutasInd/importarEmpleados.routes'); +const rutasExportarEmpleados = require('@altertex/emp/rutasInd/exportarEmpleados.routes'); const rutasLeerGrupoEmpleados = require('@altertex/emp/rutasInd/leerGrupoEmpleados.routes'); const rutasCrearGrupo = require('@altertex/emp/rutasInd/crearGrupoEmpleados.routes'); @@ -26,7 +27,9 @@ ruteador.use(RUTAS.EMPLEADOS.BASE, rutasImportarEmpleados); ruteador.use(RUTAS.EMPLEADOS.BASE, rutasLeerGrupoEmpleados); //RF21 - Crear Grupo de Empleados - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF21 ruteador.use(RUTAS.EMPLEADOS.BASE, rutasCrearGrupo); - //RF19 - Actualizar Empleado - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF19 ruteador.use(RUTAS.EMPLEADOS.BASE, rutasActualizarEmpleado); -module.exports = ruteador; +//RF59 - Exportar Empleados- https://codeandco-wiki.netlify.app/docs/next/proyectos/textiles/documentacion/requisitos/RF59 +ruteador.use(RUTAS.EMPLEADOS.BASE, rutasExportarEmpleados); + +module.exports = ruteador; \ No newline at end of file diff --git a/Utilidades/Constantes/mensajesEmpleados.js b/Utilidades/Constantes/mensajesEmpleados.js index f67fd574..c0cdd18a 100644 --- a/Utilidades/Constantes/mensajesEmpleados.js +++ b/Utilidades/Constantes/mensajesEmpleados.js @@ -50,6 +50,15 @@ module.exports = { codigo: 200, mensaje: 'Empleado(s) eliminado(s) correctamente.', }, + LISTA_EMPLEADOS_EXPORTADA: { + codigo: 200, + mensaje: 'Lista de empleados exportada exitosamente.' + }, + // 204 - No hay datos + EMPLEADOS_NO_ENCONTRADOS: { + codigo: 204, + mensaje: 'No hay empleados para exportar.' + }, // 500 - Internal Server Error ERROR_ELIMINAR_EMPLEADO: { codigo: 500, @@ -72,9 +81,12 @@ module.exports = { codigo: 500, mensaje: 'Ocurrió un error al crear el grupo de empleados.', }, - GRUPO_NOMBRE_REPETIDO: { codigo: 'GRUPO_NOMBRE_REPETIDO', mensaje: 'Ya existe un grupo con ese nombre.', }, + ERROR_EXPORTAR_EMPLEADOS: { + codigo: 500, + mensaje: 'Error al exportar la lista de empleados.' + } }; diff --git a/Utilidades/Constantes/rutas.js b/Utilidades/Constantes/rutas.js index 92946212..f83b856d 100644 --- a/Utilidades/Constantes/rutas.js +++ b/Utilidades/Constantes/rutas.js @@ -69,6 +69,7 @@ module.exports = { ELIMINAR_GRUPO: '/eliminar-grupo', ELIMINAR_EMPLEADO: '/eliminar', IMPORTAR_EMPLEADOS: '/importar-empleados', + EXPORTAR_EMPLEADOS: '/exportar-empleados', LEER_GRUPO: '/leer-grupo', CREAR_GRUPO: '/crear-grupo', ACTUALIZAR: '/actualizar', From 9553835f7be36d1ece2100ebef9e5e70a73a1e95 Mon Sep 17 00:00:00 2001 From: Diego Alfaro Pinto Date: Tue, 27 May 2025 16:37:40 -0600 Subject: [PATCH 026/116] docs: agregar documentacion jsdocs y swagger --- Configuracion/rutasSwagger.js | 1 + .../crearSetsProductos.controller.js | 28 ++++++- .../repositorioCrearSetsProductos.js | 23 ++++++ .../crearSetsProductos.routes.js | 73 +++++++++++++++++++ 4 files changed, 124 insertions(+), 1 deletion(-) diff --git a/Configuracion/rutasSwagger.js b/Configuracion/rutasSwagger.js index 4b6d5deb..b7982bb0 100644 --- a/Configuracion/rutasSwagger.js +++ b/Configuracion/rutasSwagger.js @@ -49,6 +49,7 @@ module.exports = [ './SetsProductos/Rutas/RutasIndividuales/consultarSetsProductos.routes.js', './SetsProductos/Rutas/RutasIndividuales/eliminarSetsProductos.routes.js', + './SetsProductos/Rutas/RutasIndividuales/crearSetsProductos.routes.js', './Usuarios/Rutas/RutasIndividuales/consultarListaUsuarios.routes.js', './Usuarios/Rutas/RutasIndividuales/crearUsuario.routes.js', diff --git a/SetsProductos/Controladores/crearSetsProductos.controller.js b/SetsProductos/Controladores/crearSetsProductos.controller.js index 7fba9d13..96fc79a7 100644 --- a/SetsProductos/Controladores/crearSetsProductos.controller.js +++ b/SetsProductos/Controladores/crearSetsProductos.controller.js @@ -1,9 +1,35 @@ const MENSAJES_SETS_PRODUCTOS = require('@altertex/util/const/mensajesSetsProductos'); const repositorio = require('@altertex/setspro/repos/repositorioCrearSetsProductos'); + +/** + * Controlador de Express que maneja la creación de un nuevo set de productos para un cliente. + * + * Valida: + * - Que se haya enviado el cuerpo de la solicitud con los datos requeridos. + * - Que se hayan proporcionado todos los campos necesarios. + * - Que exista un cliente seleccionado en el usuario autenticado. + * + * Llama al repositorio para realizar la lógica de negocio y persistencia, y responde + * con el estado adecuado dependiendo del resultado. + * + * @async + * @function crearSetsProductos + * @param {Express.Request} req - Objeto de solicitud de Express. + * @param {Express.Response} res - Objeto de respuesta de Express. + * + * @returns {Promise} Devuelve una respuesta HTTP con el estado correspondiente. + * + * @throws {500} En caso de error inesperado en la creación del set. + */ exports.crearSetsProductos = async (req, res) => { - const datos = req.body; + const datos = req.body.nuevoSetsProductos; const cliente = req.user.clienteSeleccionado; + if (!req.body.nuevoSetsProductos) { + return res.status(MENSAJES_SETS_PRODUCTOS.DATOS_INVALIDOS_ERROR.codigo).json({ mensaje: MENSAJES_SETS_PRODUCTOS.DATOS_INVALIDOS_ERROR.mensaje }); + } + + if (!datos.nombre || !datos.nombreVisible || !datos.descripcion || !datos.activo || !datos.idProductos) { return res.status(MENSAJES_SETS_PRODUCTOS.DATOS_INVALIDOS_ERROR.codigo).json({ mensaje: MENSAJES_SETS_PRODUCTOS.DATOS_INVALIDOS_ERROR.mensaje }); } diff --git a/SetsProductos/Datos/Repositorios/repositorioCrearSetsProductos.js b/SetsProductos/Datos/Repositorios/repositorioCrearSetsProductos.js index 6d9e4a6e..21623f34 100644 --- a/SetsProductos/Datos/Repositorios/repositorioCrearSetsProductos.js +++ b/SetsProductos/Datos/Repositorios/repositorioCrearSetsProductos.js @@ -2,6 +2,29 @@ const MENSAJES_SETS_PRODUCTOS = require('@altertex/util/const/mensajesSetsProduc const correrQuery = require('@altertex/util/ser/correrQuery'); const CONSULTAS = require('@altertex/util/const/consultasSetsProductos'); +/** + * Crea un nuevo set de productos para un cliente específico, validando que: + * - El nombre y nombre visible del set no estén duplicados. + * - Todos los productos asociados existan. + * + * Si todo es válido, crea el set de productos y asigna los productos especificados al set. + * + * @async + * @function crearSetsProductos + * @param {number} idCliente - ID del cliente al que se le asignará el set de productos. + * @param {object} datosSetsProducto - Datos del set de productos a crear. + * @param {string} datosSetsProducto.nombre - Nombre interno del set. + * @param {string} datosSetsProducto.nombreVisible - Nombre visible del set. + * @param {string} [datosSetsProducto.descripcion] - Descripción del set (opcional). + * @param {boolean} datosSetsProducto.activo - Indicador de si el set estará activo. + * @param {number[]} datosSetsProducto.idProductos - IDs de los productos que se asociarán al set. + * + * @throws {Error} Si el nombre o nombre visible del set ya están en uso. + * @throws {Error} Si uno o más productos no existen en la base de datos. + * @throws {Error} Si ocurre un error inesperado durante la ejecución. + * + * @returns {Promise} No retorna un valor directamente, pero lanza errores si algo falla. + */ exports.crearSetsProductos = async (idCliente, datosSetsProducto) => { try { diff --git a/SetsProductos/Rutas/RutasIndividuales/crearSetsProductos.routes.js b/SetsProductos/Rutas/RutasIndividuales/crearSetsProductos.routes.js index 40001fa1..22a05c6b 100644 --- a/SetsProductos/Rutas/RutasIndividuales/crearSetsProductos.routes.js +++ b/SetsProductos/Rutas/RutasIndividuales/crearSetsProductos.routes.js @@ -11,6 +11,79 @@ const controlador = require('@altertex/setspro/ctrl/crearSetsProductos.controlle const PERMISOS = require('@altertex/util/const/permisos'); const RUTAS = require('@altertex/util/const/rutas'); +/** + * @swagger + * /api/sets-productos/crear: + * post: + * summary: Crea un nuevo set de productos para un cliente autenticado. + * tags: + * - Sets de Productos + * security: + * - ApiKeyAuth: [] + * - BearerAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * nuevoSetsProductos: + * type: object + * required: + * - nombre + * - nombreVisible + * - descripcion + * - activo + * - idProductos + * properties: + * nombre: + * type: string + * example: "combo-verano" + * nombreVisible: + * type: string + * example: "Combo de Verano" + * descripcion: + * type: string + * example: "Incluye productos para la temporada de verano" + * activo: + * type: boolean + * example: true + * idProductos: + * type: array + * items: + * type: integer + * example: [1, 2, 3] + * responses: + * 201: + * description: Set de productos creado exitosamente. + * content: + * application/json: + * schema: + * type: object + * properties: + * mensaje: + * type: string + * example: "Set de productos creado correctamente." + * 400: + * description: Datos inválidos o faltantes en la solicitud. + * content: + * application/json: + * schema: + * type: object + * properties: + * mensaje: + * type: string + * example: "Los datos enviados son inválidos." + * 401: + * description: Token de autenticación no válido o ausente. + * 403: + * description: Permisos insuficientes para crear sets de productos. + * 429: + * description: Límite diario de peticiones alcanzado. + * 500: + * description: Error interno del servidor al crear el set de productos. + */ ruteador.post(RUTAS.SETS_PRODUCTOS.CREAR, validarYSanitizar, revisarApiKey(), autorizarToken, limitePeticionesDiarias, verificarPermisos(PERMISOS.CREAR_SET_PRODUCTOS), controlador.crearSetsProductos); module.exports = ruteador; \ No newline at end of file From 86a34299d0020ccba53372b5be59bab5550638d6 Mon Sep 17 00:00:00 2001 From: NicoH00d Date: Tue, 27 May 2025 22:10:06 -0600 Subject: [PATCH 027/116] docs: js docs en controlador de crear producto --- .../importarProductos.controller.js | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/Productos/Controladores/importarProductos.controller.js b/Productos/Controladores/importarProductos.controller.js index de672841..9c5adc7d 100644 --- a/Productos/Controladores/importarProductos.controller.js +++ b/Productos/Controladores/importarProductos.controller.js @@ -6,6 +6,32 @@ const repositorioCrearVariante = require('@altertex/pro/repos/repositorioCrearVa const repositorioCrearOpcion = require('@altertex/pro/repos/repositorioCrearOpcion'); const db = require('@altertex/util/bd/db'); + +/** + * Importa productos y sus variantes/opciones para un cliente. + * + * Espera en req.body un array de objetos con la forma: + * [ + * { + * producto: { ... }, + * variantes: [ + * { + * ..., + * opciones: { ... } + * } + * ] + * } + * ] + * + * Valida cada producto, variante y opciones antes de insertar en la base de datos. + * Si hay errores en alguna fila, los acumula y los devuelve al finalizar. + * + * @async + * @function importarProductos + * @param {Express.Request} req - Request de Express, requiere req.user.clienteSeleccionado y req.body. + * @param { Express.Response} res - Response de Express. + * @returns {Promise} Devuelve un JSON con el resultado de la importación y los errores encontrados. + */ exports.importarProductos = async (req, res) => { console.dir(req.body, { depth: null }); const idCliente = parseInt(req.user.clienteSeleccionado); @@ -22,7 +48,7 @@ exports.importarProductos = async (req, res) => { conexion = await db.getConnection(); await conexion.beginTransaction(); - for (let im = 0; im < productos.length; im++) { + for (let im = 0; im < productos.length; im = im + 1) { const { producto, variantes } = productos[im]; const fila = im + 1; From e9be560573692394c6090646f5e3e52309451448 Mon Sep 17 00:00:00 2001 From: NicoH00d Date: Tue, 27 May 2025 22:36:27 -0600 Subject: [PATCH 028/116] =?UTF-8?q?feature:=20cambiar=20consulta=20para=20?= =?UTF-8?q?que=20tambi=C3=A9n=20devuelva=20los=20productos=20que=20no=20ti?= =?UTF-8?q?enen=20imagen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Utilidades/Constantes/consultasProductos.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Utilidades/Constantes/consultasProductos.js b/Utilidades/Constantes/consultasProductos.js index 81b5df4c..07885eeb 100644 --- a/Utilidades/Constantes/consultasProductos.js +++ b/Utilidades/Constantes/consultasProductos.js @@ -2,10 +2,9 @@ module.exports = { OBTENER_LISTA: ` SELECT p.idProducto, p.nombreComun, p.precioVenta, p.estado, i.urlImagen FROM producto p - JOIN imagen_producto ip ON p.idProducto = ip.idProducto - JOIN imagen i ON ip.idImagen = i.idImagen - WHERE i.tipoImagen = "Imagen Producto" - AND p.idCliente = ?; + LEFT JOIN imagen_producto ip ON p.idProducto = ip.idProducto + LEFT JOIN imagen i ON ip.idImagen = i.idImagen AND i.tipoImagen = "Imagen Producto" + WHERE p.idCliente = ?; `, CREAR: ` INSERT INTO producto ( From 0c2a63069d493f1a204bd612ffe04e480c2539d8 Mon Sep 17 00:00:00 2001 From: Hiram <147564077+Hiram10tec@users.noreply.github.com> Date: Wed, 28 May 2025 11:14:25 -0600 Subject: [PATCH 029/116] =?UTF-8?q?feat(Categor=C3=ADas):=20actualizar=20c?= =?UTF-8?q?ategor=C3=ADa?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../actualizarCategoria.controller.js | 33 +++++++++++++++++++ .../repositorioActualizarCategorias.js | 22 +++++++++++++ .../actualizarCategorias.routes.js | 22 +++++++++++++ Categorias/Rutas/indexCategorias.routes.js | 2 ++ Utilidades/Constantes/consultasCategorias.js | 17 +++++++++- Utilidades/Constantes/rutas.js | 1 + 6 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 Categorias/Controladores/actualizarCategoria.controller.js create mode 100644 Categorias/Datos/Repositorios/repositorioActualizarCategorias.js create mode 100644 Categorias/Rutas/RutasIndividuales/actualizarCategorias.routes.js diff --git a/Categorias/Controladores/actualizarCategoria.controller.js b/Categorias/Controladores/actualizarCategoria.controller.js new file mode 100644 index 00000000..295391d7 --- /dev/null +++ b/Categorias/Controladores/actualizarCategoria.controller.js @@ -0,0 +1,33 @@ +const { actualizarCategoria } = require('@altertex/cat/repos/repositorioActualizarCategorias'); +const MENSAJES = require('@altertex/util/const/mensajesCategorias'); + +/** + * RF49 - Actualizar categoría de productos - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF49 + * + * @param {import('express').Request} req + * @param {import('express').Response} res + * @returns {Promise} + */ +exports.actualizarCategoria = async (req, res) => { + try { + const { idCategoria } = req.params; + const { nombreCategoria, descripcion, productos } = req.body; + + if (!idCategoria || !nombreCategoria || typeof nombreCategoria !== 'string') { + return res.status(400).json(MENSAJES.PARAMETROS_INVALIDOS); + } + + await actualizarCategoria({ idCategoria, nombreCategoria, descripcion, productos }); + + return res.status(200).json({ + codigo: 200, + mensaje: 'Categoría actualizada correctamente.', + }); + } catch (error) { + console.error('Error al actualizar categoría:', error); + return res.status(500).json({ + codigo: 500, + mensaje: 'Ocurrió un error al actualizar la categoría.', + }); + } +}; \ No newline at end of file diff --git a/Categorias/Datos/Repositorios/repositorioActualizarCategorias.js b/Categorias/Datos/Repositorios/repositorioActualizarCategorias.js new file mode 100644 index 00000000..b3c72b05 --- /dev/null +++ b/Categorias/Datos/Repositorios/repositorioActualizarCategorias.js @@ -0,0 +1,22 @@ +const correrQuery = require('@altertex/util/ser/correrQuery'); +const CONSULTAS = require('@altertex/util/const/consultasCategorias'); + +/** + * Actualiza el nombre, descripción y productos de una categoría. + * + * @param {object} categoria - Objeto con los datos de la categoría. + * @param {number} categoria.idCategoria - ID de la categoría a actualizar. + * @param {string} categoria.nombreCategoria - Nuevo nombre de la categoría. + * @param {string} categoria.descripcion - Nueva descripción. + * @param {number[]} categoria.productos - IDs de productos asociados. + * @returns {Promise} + */ +exports.actualizarCategoria = async ({ idCategoria, nombreCategoria, descripcion, productos }) => { + await correrQuery(CONSULTAS.ACTUALIZAR_CATEGORIA, [nombreCategoria, descripcion, idCategoria]); + await correrQuery(CONSULTAS.ELIMINAR_PRODUCTOS_CATEGORIA, [idCategoria]); + + if (productos && productos.length > 0) { + const valores = productos.map((idProd) => [idCategoria, idProd]); + await correrQuery(CONSULTAS.ASIGNAR_PRODUCTOS_A_CATEGORIA, [valores]); + } +}; \ No newline at end of file diff --git a/Categorias/Rutas/RutasIndividuales/actualizarCategorias.routes.js b/Categorias/Rutas/RutasIndividuales/actualizarCategorias.routes.js new file mode 100644 index 00000000..34a74260 --- /dev/null +++ b/Categorias/Rutas/RutasIndividuales/actualizarCategorias.routes.js @@ -0,0 +1,22 @@ +/** + * RF49 - Actualizar categoría de productos - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF49 + */ + +const express = require('express'); +const ruteador = express.Router(); +const controlador = require('@altertex/cat/ctrl/actualizarCategoria.controller'); +const revisarApiKey = require('@altertex/util/inter/revisarApiKey'); +const autorizarToken = require('@altertex/util/inter/autorizarToken'); +const verificarPermisos = require('@altertex/util/inter/verificarPermisos'); +const PERMISOS = require('@altertex/util/const/permisos'); +const RUTAS = require('@altertex/util/const/rutas'); + +ruteador.put( + `${RUTAS.CATEGORIAS.ACTUALIZAR}/:idCategoria`, + revisarApiKey(), + autorizarToken, + verificarPermisos(PERMISOS.ACTUALIZAR_CATEGORIA_PRODUCTOS), + controlador.actualizarCategoria +); + +module.exports = ruteador; \ No newline at end of file diff --git a/Categorias/Rutas/indexCategorias.routes.js b/Categorias/Rutas/indexCategorias.routes.js index 35f1bef5..d412692f 100644 --- a/Categorias/Rutas/indexCategorias.routes.js +++ b/Categorias/Rutas/indexCategorias.routes.js @@ -4,6 +4,7 @@ const rutasConsultarListaCategorias = require('@altertex/cat/rutasInd/consultarL const rutasCrearCategoria = require('@altertex/cat/rutasInd/crearCategoria.routes'); const rutasEliminarCategoria = require('@altertex/cat/rutasInd/eliminarCategoria.routes'); const rutasLeerCategoria = require('@altertex/cat/rutasInd/consultarDetalleCategoria.routes'); +const rutasActualizarCategoria = require('@altertex/cat/rutasInd/actualizarCategorias.routes'); const RUTAS = require('@altertex/util/const/rutas'); @@ -11,5 +12,6 @@ ruteador.use(RUTAS.CATEGORIAS.BASE, rutasConsultarListaCategorias); ruteador.use(RUTAS.CATEGORIAS.BASE, rutasCrearCategoria); ruteador.use(RUTAS.CATEGORIAS.BASE, rutasEliminarCategoria); ruteador.use(RUTAS.CATEGORIAS.BASE, rutasLeerCategoria); +ruteador.use(RUTAS.CATEGORIAS.BASE, rutasActualizarCategoria); module.exports = ruteador; diff --git a/Utilidades/Constantes/consultasCategorias.js b/Utilidades/Constantes/consultasCategorias.js index 7715e286..23acdd34 100644 --- a/Utilidades/Constantes/consultasCategorias.js +++ b/Utilidades/Constantes/consultasCategorias.js @@ -48,7 +48,7 @@ module.exports = { WHERE idProducto IN (?); `, - LEER_DETALLE_CATEGORIA: ` + LEER_DETALLE_CATEGORIA: ` SELECT c.idCategoria, c.nombreCategoria, @@ -60,4 +60,19 @@ module.exports = { LEFT JOIN producto p ON cp.idProducto = p.idProducto WHERE c.idCategoria = ?; `, + + ACTUALIZAR_CATEGORIA: ` + UPDATE categoria + SET nombreCategoria = ?, descripcion = ? + WHERE idCategoria = ?; + `, + + ELIMINAR_PRODUCTOS_CATEGORIA: ` + DELETE FROM categoria_producto WHERE idCategoria = ?; + `, + + ASIGNAR_PRODUCTOS_A_CATEGORIA: ` + INSERT INTO categoria_producto (idCategoria, idProducto) + VALUES ?; + `, }; diff --git a/Utilidades/Constantes/rutas.js b/Utilidades/Constantes/rutas.js index 11d9254e..6b8a774b 100644 --- a/Utilidades/Constantes/rutas.js +++ b/Utilidades/Constantes/rutas.js @@ -26,6 +26,7 @@ module.exports = { CREAR: '/crear', ELIMINAR_USUARIOS: '/eliminar-usuarios', LEER: '/leer', + ACTUALIZAR: '/actualizar', }, EVENTOS: { BASE: '/eventos', From 7a52c28ac6637baeee314010855d6623a03c2982 Mon Sep 17 00:00:00 2001 From: NicoH00d Date: Wed, 28 May 2025 11:25:08 -0600 Subject: [PATCH 030/116] fix: eliminar comentarios --- Productos/Controladores/importarProductos.controller.js | 1 - 1 file changed, 1 deletion(-) diff --git a/Productos/Controladores/importarProductos.controller.js b/Productos/Controladores/importarProductos.controller.js index 9c5adc7d..57e85aa2 100644 --- a/Productos/Controladores/importarProductos.controller.js +++ b/Productos/Controladores/importarProductos.controller.js @@ -33,7 +33,6 @@ const db = require('@altertex/util/bd/db'); * @returns {Promise} Devuelve un JSON con el resultado de la importación y los errores encontrados. */ exports.importarProductos = async (req, res) => { - console.dir(req.body, { depth: null }); const idCliente = parseInt(req.user.clienteSeleccionado); const productos = req.body; // Espera array de { producto, variantes } From ea9aefdcff511516f68d63c0dc61ab486769660c Mon Sep 17 00:00:00 2001 From: Hiram <147564077+Hiram10tec@users.noreply.github.com> Date: Wed, 28 May 2025 12:42:22 -0600 Subject: [PATCH 031/116] Fix(productos): se eliminan imagenes con placeholder o imagenes --- .../eliminarProducto.controller.js | 14 ++------ .../Repositorios/productosRepositorio.js | 36 +++++++++---------- Utilidades/Constantes/consultasProductos.js | 9 +++++ 3 files changed, 29 insertions(+), 30 deletions(-) diff --git a/Productos/Controladores/eliminarProducto.controller.js b/Productos/Controladores/eliminarProducto.controller.js index bbcdd56f..8e33cca8 100644 --- a/Productos/Controladores/eliminarProducto.controller.js +++ b/Productos/Controladores/eliminarProducto.controller.js @@ -1,6 +1,5 @@ // Importación de la función que elimina productos en el repositorio de datos. const { eliminarProductos } = require('@altertex/pro/repos/productosRepositorio'); -const eliminarImagenS3 = require('@altertex/util/ser/eliminarImagenS3'); // Importación de las constantes de mensajes utilizados para respuestas del módulo de productos. const { @@ -30,7 +29,7 @@ const { */ const eliminarProductoController = async (req, res) => { try { - const { ids, imagenes } = req.body; + const { ids } = req.body; // Validación de los IDs recibidos. if (!Array.isArray(ids) || ids.length === 0) { @@ -39,17 +38,8 @@ const eliminarProductoController = async (req, res) => { mensaje: 'Debes proporcionar al menos un ID de producto para eliminar.', }); } - - // Validación de las imágenes recibidas. - if (Array.isArray(imagenes)) { - imagenes.forEach((url) => { - const parts = url.split('/'); - const filename = parts[parts.length - 1]; - eliminarImagenS3('productos/', filename); - }); - } // Se realiza la eliminación de los productos. - const resultado = await eliminarProductos(ids, imagenes); + const resultado = await eliminarProductos(ids); // Se responde dependiendo del éxito o fallo de la operación. if (resultado) { diff --git a/Productos/Datos/Repositorios/productosRepositorio.js b/Productos/Datos/Repositorios/productosRepositorio.js index dc6012e4..5af72a09 100644 --- a/Productos/Datos/Repositorios/productosRepositorio.js +++ b/Productos/Datos/Repositorios/productosRepositorio.js @@ -1,6 +1,6 @@ // RF[30] Eliminar Producto - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF30] const correrQuery = require('@altertex/util/ser/correrQuery'); -const { ELIMINAR_PRODUCTOS } = require('@altertex/util/const/consultasProductos'); +const consultas = require('@altertex/util/const/consultasProductos'); const extraerNombreArchivoS3 = require('@altertex/util/ser/extraerNombreArchivoS3'); const eliminarImagenS3 = require('@altertex/util/ser/eliminarImagenS3'); @@ -19,26 +19,26 @@ const eliminarImagenS3 = require('@altertex/util/ser/eliminarImagenS3'); */ const eliminarProductos = async (ids) => { try { - // 1. Obtener imágenes de los productos antes de borrarlos - const obtenerQuery = ` - SELECT i.urlImagen FROM producto p - JOIN imagen_producto ip ON p.idProducto = ip.idProducto - JOIN imagen i ON ip.idImagen = i.idImagen - WHERE p.idProducto IN (${ids.map(() => '?').join(',')}); - `; - const imagenes = await correrQuery(obtenerQuery, ids); + // 1. Obtener imágenes asociadas + const placeholders = ids.map(() => '?').join(','); + const queryObtenerImagenes = consultas.OBTENER_IMAGENES_POR_IDS.replace('(?)', `(${placeholders})`); + const imagenes = await correrQuery(queryObtenerImagenes, ids); - // 2. Borrar las imágenes en S3 + // 2. Eliminar imágenes válidas for (const img of imagenes) { - const nombreReal = extraerNombreArchivoS3(img.urlImagen); - eliminarImagenS3('productos/', nombreReal); + if (img.urlImagen && !img.urlImagen.includes('placeholder')) { + const nombreReal = extraerNombreArchivoS3(img.urlImagen); + if (nombreReal) { + await eliminarImagenS3('productos/', nombreReal); + } + } } - // 3. Eliminar productos en base de datos - const placeholders = ids.map(() => '?').join(','); - const query = ELIMINAR_PRODUCTOS.replace('(?)', `(${placeholders})`); - const resultado = await correrQuery(query, ids); - return resultado.affectedRows > 0; + // 3. Eliminar productos + const queryEliminar = consultas.ELIMINAR_PRODUCTOS.replace('(?)', `(${placeholders})`); + const resultado = await correrQuery(queryEliminar, ids); + + return resultado?.affectedRows > 0; } catch { return false; } @@ -46,4 +46,4 @@ const eliminarProductos = async (ids) => { module.exports = { eliminarProductos, -}; +}; \ No newline at end of file diff --git a/Utilidades/Constantes/consultasProductos.js b/Utilidades/Constantes/consultasProductos.js index 07885eeb..0f620aee 100644 --- a/Utilidades/Constantes/consultasProductos.js +++ b/Utilidades/Constantes/consultasProductos.js @@ -25,4 +25,13 @@ module.exports = { ELIMINAR_PRODUCTOS: "DELETE FROM producto WHERE idProducto IN (?)", + + OBTENER_IMAGENES_POR_IDS: ` + SELECT p.idProducto, i.urlImagen + FROM producto p + LEFT JOIN imagen_producto ip ON p.idProducto = ip.idProducto + LEFT JOIN imagen i ON ip.idImagen = i.idImagen + WHERE p.idProducto IN (?); + `, + }; From a7f01ed479b0dfe48a0ba9b08a2132a256459102 Mon Sep 17 00:00:00 2001 From: Krlos7121 Date: Thu, 29 May 2025 15:55:31 -0600 Subject: [PATCH 032/116] feature: repositorio para crear un empleado --- .../Controladores/crearEmpleado.controller.js | 161 +++++++++++++++++- .../Repositorios/repositorioCrearEmpleado.js | 127 +++++++++----- .../repositorioImportarEmpleado.js | 83 ++++----- dump.rdb | Bin 0 -> 88 bytes 4 files changed, 282 insertions(+), 89 deletions(-) create mode 100644 dump.rdb diff --git a/Empleados/Controladores/crearEmpleado.controller.js b/Empleados/Controladores/crearEmpleado.controller.js index 4cded3f6..57c32c2b 100644 --- a/Empleados/Controladores/crearEmpleado.controller.js +++ b/Empleados/Controladores/crearEmpleado.controller.js @@ -1,3 +1,4 @@ +const bcrypt = require('bcryptjs'); const MENSAJES = require('@altertex/util/const/mensajesEmpleados'); const repositorio = require('@altertex/emp/repos/repositorioCrearEmpleado'); //RF[16] Crear empleado - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF16] @@ -9,9 +10,159 @@ const repositorio = require('@altertex/emp/repos/repositorioCrearEmpleado'); * * @function crearEmpleado * @async - * @param {Express.Request} req - Objeto de solicitud HTTP de Express. - * @param {object} req.body - Cuerpo de la solicitud. - * @param {object} req.body.empleado - Información del nuevo empleado a crear. - * @param {Express.Response} res - Objeto de respuesta HTTP de Express. - * @returns {Promise} Retorna una respuesta JSON indicando éxito o un error. + * @param {Array} req - Objeto de solicitud HTTP de Express. + * @param {Array} req.body - Cuerpo de la solicitud con los datos del nuevo empleado. + * @param {string} req.body[].nombreCompleto - Nombre completo del usuario. + * @param {string} req.body[].correoElectronico - Correo electrónico único del usuario. + * @param {string} req.body[].contrasena - Contraseña en texto plano. + * @param {string} req.body[].numeroTelefono - Número de teléfono (10 dígitos). + * @param {string} req.body[].direccion - Dirección del usuario. + * @param {string} req.body[].fechaNacimiento - Fecha de nacimiento en formato YYYY-MM-DD. + * @param {string} req.body[].genero - Género del usuario. + * @param {boolean} req.body[].estatus - Estatus activo/inactivo del usuario. + * @param {number} req.body[].idRol - ID del rol asignado al usuario. + * @param {number|Array} req.body[].idCliente - ID(s) de cliente asociados. + * @param {string} req.body[].numeroEmergencia - Teléfono de emergencia del empleado. + * @param {string} req.body[].areaTrabajo - Área donde trabaja el empleado. + * @param {string} req.body[].posicion - Puesto del empleado. + * @param {number} req.body[].cantidadPuntos - Puntos iniciales del empleado. + * @param {string} req.body[].antiguedad - Fecha de ingreso (YYYY-MM-DD). + * @param {response} res - Objeto de respuesta HTTP. + * @returns {Promise} + * + * - 200 si el empleado se creó correctamente. + * - 400 si el cuerpo está vacío o no es un arreglo. + * - 500 si ocurre un error al crear el empleado. */ +exports.crearEmpleado = async (req, res) => { + const idCliente = parseInt(req.user.clienteSeleccionado); + const empleado = req.body; + + if (!empleado || typeof empleado !== 'object' || Array.isArray(empleado)) { + return res.status(400).json({ mensaje: MENSAJES.DATOS_INCOMPLETOS.mensaje }); + } + + const errores = []; + const { + nombreCompleto, + correoElectronico, + contrasena, + numeroTelefono, + direccion, + fechaNacimiento, + genero, + estatus, + idRol, + idCliente: clienteId, + numeroEmergencia, + areaTrabajo, + posicion, + cantidadPuntos, + antiguedad, + } = empleado; + + // Validaciones de campos requeridos + if ( + !nombreCompleto || + !correoElectronico || + !contrasena || + !numeroTelefono || + !direccion || + !fechaNacimiento || + !genero || + estatus === undefined || + idRol === undefined || + !numeroEmergencia || + !areaTrabajo || + !posicion || + cantidadPuntos === undefined || + !antiguedad + ) { + return res.status(400).json({ mensaje: MENSAJES.DATOS_INCOMPLETOS.mensaje }); + } + + // Validaciones de longitud y formato + if (nombreCompleto.length > 75) { + errores.push({ campo: 'nombreCompleto', error: 'El nombre es demasiado largo' }); + } + if (correoElectronico.length > 75) { + errores.push({ campo: 'correoElectronico', error: 'El correo es demasiado largo' }); + } + if (contrasena.length > 75) { + errores.push({ campo: 'contrasena', error: 'La contraseña es demasiado larga' }); + } + if (direccion.length > 150) { + errores.push({ campo: 'direccion', error: 'La dirección es demasiado larga' }); + } + if (posicion.length > 75) { + errores.push({ campo: 'posicion', error: 'La posición es demasiado larga' }); + } + if (areaTrabajo.length > 75) { + errores.push({ campo: 'areaTrabajo', error: 'El área de trabajo es demasiado larga' }); + } + if (genero.length > 20) { + errores.push({ campo: 'genero', error: 'El género es demasiado largo' }); + } + + // Validación de estatus nulo + if (estatus == null) { + errores.push({ campo: 'estatus', error: 'Estatus inválido: debe ser 0 o 1' }); + } + + // Validación de idCliente (no debe venir en el body) + if (typeof clienteId !== 'undefined' && clienteId !== '' && clienteId !== null) { + errores.push({ campo: 'idCliente', error: 'El cliente no debe ser incluido en el archivo' }); + } + + // Validación de número de emergencia (numérico) + if (isNaN(numeroEmergencia)) { + errores.push({ campo: 'numeroEmergencia', error: 'El número de emergencia no es válido' }); + } + + // Validación de correo electrónico válido + const correoValido = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!correoValido.test(correoElectronico)) { + errores.push({ campo: 'correoElectronico', error: 'El correo electrónico no es válido' }); + } + + // Validación de contraseña fuerte + const tieneCaracterEspecial = /[!@#$%^&*(),.?":{}|<>]/; + const tieneMayuscula = /[A-Z]/; + if ( + contrasena.length < 8 || + !tieneCaracterEspecial.test(contrasena) || + !tieneMayuscula.test(contrasena) + ) { + errores.push({ + campo: 'contrasena', + error: + 'La contraseña es débil. Debe tener al menos 8 caracteres, una mayúscula y un caracter especial.', + }); + } + + // Validación de teléfono válido + const telefonoValido = /^\d{10}$/; + if (!telefonoValido.test(numeroTelefono)) { + errores.push({ + campo: 'numeroTelefono', + error: 'El número de teléfono debe tener 10 dígitos numéricos', + }); + } + + if (errores.length > 0) { + return res.status(400).json({ errores }); + } + + try { + // Encriptar la contraseña antes de guardarla + const contrasenaEncriptada = await bcrypt.hash(contrasena, 10); + empleado.contrasena = contrasenaEncriptada; + + // Crear el empleado usando el repositorio + const nuevoEmpleado = await repositorio.crearEmpleado(empleado, idCliente); + return res.status(201).json(nuevoEmpleado); + } catch (error) { + console.error('Error al crear el empleado:', error); + return res.status(500).json({ mensaje: MENSAJES.ERROR_CREAR.mensaje }); + } +}; diff --git a/Empleados/Datos/Repositorios/repositorioCrearEmpleado.js b/Empleados/Datos/Repositorios/repositorioCrearEmpleado.js index b1959f78..65406cd1 100644 --- a/Empleados/Datos/Repositorios/repositorioCrearEmpleado.js +++ b/Empleados/Datos/Repositorios/repositorioCrearEmpleado.js @@ -1,3 +1,4 @@ +const db = require('@altertex/util/bd/db'); const correrQuery = require('@altertex/util/ser/correrQuery'); const MENSAJES = require('@altertex/util/const/mensajesEmpleados'); const CONSULTAS_EMPLEADOS = require('@altertex/util/const/consultasEmpleados'); @@ -5,49 +6,99 @@ const CONSULTAS_EMPLEADOS = require('@altertex/util/const/consultasEmpleados'); //RF[16] Crear empleado - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF16] /** - * Repositorio para insertar un nuevo empleado en la base de datos. + * Crea un nuevo empleado y su usuario asociado en la base de datos. * - * Este repositorio recibe un array con la información del nuevo empleado - * y realiza la inserción en la base de datos. - * - * @function insertarEmpleado * @async - * @param {Array<{ idEmpleado: number, idUsuario: number, idCliente: number, numeroEmergencia: - * number, areaTrabajo: string, posicion: string, - * cantidadPuntos: number, antiguedad: Date}>} datos - Información del nuevo empleado a insertar. - * @throws {Error} Si el arreglo está vacío o si ocurre un error en la base de datos. - * @returns {Promise} Promesa que se resuelve cuando la inserción ha sido exitosa. + * @function crearEmpleado + * @param {Object} empleado - Objeto con los datos del nuevo empleado. + * @param {string} empleado.nombreCompleto - Nombre completo del usuario. + * @param {string} empleado.correoElectronico - Correo electrónico único del usuario. + * @param {string} empleado.contrasena - Contraseña en texto plano (ya hasheada). + * @param {string} empleado.numeroTelefono - Teléfono del usuario (exactamente 10 dígitos). + * @param {string} empleado.direccion - Dirección del usuario. + * @param {string} empleado.fechaNacimiento - Fecha de nacimiento (YYYY-MM-DD). + * @param {string} empleado.genero - Género del usuario. + * @param {boolean} empleado.estatus - Estatus del usuario (true = activo, false = inactivo). + * @param {number} empleado.idRol - ID del rol asignado al usuario. + * @param {number} empleado.idCliente - ID del cliente asociado al usuario. + * @param {string} empleado.numeroEmergencia - Teléfono de emergencia del empleado. + * @param {string} empleado.areaTrabajo - Área de trabajo del empleado. + * @param {string} empleado.posicion - Puesto o cargo del empleado. + * @param {number} empleado.cantidadPuntos - Puntos acumulados del empleado. + * @param {string} empleado.antiguedad - Fecha de antigüedad/ingreso (YYYY-MM-DD). + * @throws {Error} Si el parámetro "empleado" no es un objeto válido o está vacío. + * @throws {Error} Si ocurre cualquier fallo durante la inserción en la transacción. + * + * @returns {Promise} Resuelve con un objeto que contiene el ID del nuevo empleado y su usuario. */ - -exports.insertarEmpleado = async (datos) => { - if (!Array.isArray(datos) || datos.length === 0) { - throw new Error('Sin datos para insertar.'); +exports.crearEmpleado = async (empleado) => { + if (!empleado || typeof empleado !== 'object') { + throw new Error('No se recibió un empleado válido para importar.'); } + + const conn = await db.getConnection(); + try { - await Promise.all( - datos.map( - ({ - idUsuario, - idCliente, - numeroEmergencia, - areaTrabajo, - posicion, - cantidadPuntos, - antiguedad, - }) => { - return correrQuery(CONSULTAS_EMPLEADOS.INSERTAR_EMPLEADO, [ - idUsuario, - idCliente, - numeroEmergencia, - areaTrabajo, - posicion, - cantidadPuntos, - antiguedad, - ]); - } - ) + await conn.beginTransaction(); + + // Validar correo duplicado + const [correoExistente] = await conn.query( + CONSULTAS_IMPORTAR_EMPLEADOS.VALIDAR_CORREOS_DUPLICADOS, + [[empleado.correoElectronico]] ); - } catch (error) { - throw new Error(MENSAJES.ERROR_CREAR.mensaje); + if (correoExistente.length > 0) { + throw new Error(`Correo ya registrado: ${empleado.correoElectronico}`); + } + + // Validar teléfono duplicado + const [telefonoExistente] = await conn.query( + CONSULTAS_IMPORTAR_EMPLEADOS.VALIDAR_TELEFONO_DUPLICADO, + [[empleado.numeroTelefono]] + ); + if (telefonoExistente.length > 0) { + throw new Error(`Teléfono ya registrado: ${empleado.numeroTelefono}`); + } + + // Insertar usuario + const [resultadoUsuario] = await conn.query(CONSULTAS_IMPORTAR_EMPLEADOS.INSERTAR_USUARIO, [ + empleado.nombreCompleto, + empleado.correoElectronico, + empleado.contrasena, + empleado.numeroTelefono, + empleado.direccion, + empleado.fechaNacimiento, + empleado.genero, + empleado.estatus, + ]); + const idUsuario = resultadoUsuario.insertId; + + // Insertar rol + await conn.query(CONSULTAS_IMPORTAR_EMPLEADOS.INSERTAR_ROL, [idUsuario, DEFAULT_ROLE_ID]); + + // Insertar asociación usuario-cliente + const listaClientes = Array.isArray(empleado.idCliente) + ? empleado.idCliente + : [empleado.idCliente]; + for (const idCli of listaClientes) { + await conn.query(CONSULTAS_IMPORTAR_EMPLEADOS.INSERTAR_USUARIO_CLIENTE, [idUsuario, idCli]); + } + + // Insertar empleado + await conn.query(CONSULTAS_IMPORTAR_EMPLEADOS.INSERTAR_EMPLEADO, [ + idUsuario, + empleado.idCliente, + empleado.numeroEmergencia, + empleado.areaTrabajo, + empleado.posicion, + parseFloat(empleado.cantidadPuntos), + empleado.antiguedad, + ]); + + await conn.commit(); + } catch (err) { + await conn.rollback(); + throw new Error(`Error en importación: ${err.message}`); + } finally { + if (conn) conn.release(); } }; diff --git a/Empleados/Datos/Repositorios/repositorioImportarEmpleado.js b/Empleados/Datos/Repositorios/repositorioImportarEmpleado.js index d66469fa..9a0741c1 100644 --- a/Empleados/Datos/Repositorios/repositorioImportarEmpleado.js +++ b/Empleados/Datos/Repositorios/repositorioImportarEmpleado.js @@ -1,5 +1,5 @@ const db = require('@altertex/util/bd/db'); -const DEFAULT_ROLE_ID = 3; +const DEFAULT_ROLE_ID = 3; // ID del rol por defecto para empleados const CONSULTAS_IMPORTAR_EMPLEADOS = require('@altertex/util/const/consultasImportarEmpleados'); /** * Importa en bloque múltiples empleados, creando sus usuarios, asignando rol y vinculación con clientes. @@ -40,29 +40,29 @@ exports.importarEmpleadosMasivo = async (empleados) => { await conn.beginTransaction(); // 1) Validar correos duplicados en bloque - const correos = empleados.map(elemento => elemento.correoElectronico); + const correos = empleados.map((elemento) => elemento.correoElectronico); const [correosExistentes] = await conn.query( CONSULTAS_IMPORTAR_EMPLEADOS.VALIDAR_CORREOS_DUPLICADOS, [correos] ); if (correosExistentes.length > 0) { - const lista = correosExistentes.map(fila => fila.correoElectronico).join(', '); + const lista = correosExistentes.map((fila) => fila.correoElectronico).join(', '); throw new Error(`Correos ya registrados: ${lista}`); } // 2) Validar teléfonos duplicados en bloque - const telefonos = empleados.map(elemento => elemento.numeroTelefono); + const telefonos = empleados.map((elemento) => elemento.numeroTelefono); const [telefonosExistentes] = await conn.query( CONSULTAS_IMPORTAR_EMPLEADOS.VALIDAR_TELEFONO_DUPLICADO, [telefonos] ); if (telefonosExistentes.length > 0) { - const lista = telefonosExistentes.map(fila => fila.numeroTelefono).join(', '); + const lista = telefonosExistentes.map((fila) => fila.numeroTelefono).join(', '); throw new Error(`Teléfonos ya registrados: ${lista}`); } // 3) Bulk‐insert de usuarios - const usuariosValues = empleados.map(elemento => [ + const usuariosValues = empleados.map((elemento) => [ elemento.nombreCompleto, elemento.correoElectronico, elemento.contrasena, @@ -70,81 +70,72 @@ exports.importarEmpleadosMasivo = async (empleados) => { elemento.direccion, elemento.fechaNacimiento, elemento.genero, - elemento.estatus + elemento.estatus, ]); - await conn.query( - CONSULTAS_IMPORTAR_EMPLEADOS.INSERTAR_USUARIO_EN_VOLUMEN, - [usuariosValues] - ); + await conn.query(CONSULTAS_IMPORTAR_EMPLEADOS.INSERTAR_USUARIO_EN_VOLUMEN, [usuariosValues]); // 4) Recuperar los IDs generados - const [rowsUsuarios] = await conn.query( - CONSULTAS_IMPORTAR_EMPLEADOS.OBTENER_ID_GENERADOS, - [correos] - ); + const [rowsUsuarios] = await conn.query(CONSULTAS_IMPORTAR_EMPLEADOS.OBTENER_ID_GENERADOS, [ + correos, + ]); const idMap = rowsUsuarios.reduce((map, row) => { map[row.correoElectronico] = row.idUsuario; return map; }, {}); // 5) Bulk‐insert de roles - const rolValues = empleados.map(elemento => [ + const rolValues = empleados.map((elemento) => [ idMap[elemento.correoElectronico], - DEFAULT_ROLE_ID + DEFAULT_ROLE_ID, ]); - await conn.query( - CONSULTAS_IMPORTAR_EMPLEADOS.INSERTAR_ROLES_EN_VOLUMEN, - [rolValues] - ); + await conn.query(CONSULTAS_IMPORTAR_EMPLEADOS.INSERTAR_ROLES_EN_VOLUMEN, [rolValues]); // 6) Bulk‐insert de asociaciones usuario‐cliente const clienteValues = []; - empleados.forEach(elemento => { + empleados.forEach((elemento) => { const idU = idMap[elemento.correoElectronico]; - const listaClientes = Array.isArray(elemento.idCliente) ? elemento.idCliente : [elemento.idCliente]; - listaClientes.forEach(idCli => clienteValues.push([idU, idCli])); + const listaClientes = Array.isArray(elemento.idCliente) + ? elemento.idCliente + : [elemento.idCliente]; + listaClientes.forEach((idCli) => clienteValues.push([idU, idCli])); }); - await conn.query( - CONSULTAS_IMPORTAR_EMPLEADOS.INSERTAR_USUARIO_CLIENTE_EN_VOLUMEN, - [clienteValues] - ); + await conn.query(CONSULTAS_IMPORTAR_EMPLEADOS.INSERTAR_USUARIO_CLIENTE_EN_VOLUMEN, [ + clienteValues, + ]); // 7) Bulk‐insert de empleados - const empValues = empleados.map(elemento => [ + const empValues = empleados.map((elemento) => [ idMap[elemento.correoElectronico], elemento.idCliente, elemento.numeroEmergencia, elemento.areaTrabajo, elemento.posicion, parseFloat(elemento.cantidadPuntos), - elemento.antiguedad + elemento.antiguedad, ]); - await conn.query( - CONSULTAS_IMPORTAR_EMPLEADOS.INSERTAR_EMPLEADOS_EN_VOLUMEN, - [empValues] - ); + await conn.query(CONSULTAS_IMPORTAR_EMPLEADOS.INSERTAR_EMPLEADOS_EN_VOLUMEN, [empValues]); await conn.commit(); } catch (err) { await conn.rollback(); const mensajeOriginal = err.message || ''; - // Detectar error por entrada duplicada - const entradaDuplicada = mensajeOriginal.match(/Duplicate entry '(.+)' for key '(.+)'/); + // Detectar error por entrada duplicada + const entradaDuplicada = mensajeOriginal.match(/Duplicate entry '(.+)' for key '(.+)'/); - if (entradaDuplicada) { - const valorDuplicado = entradaDuplicada[1]; - const campo = entradaDuplicada[2]; + if (entradaDuplicada) { + const valorDuplicado = entradaDuplicada[1]; + const campo = entradaDuplicada[2]; - let campoTraducido = campo; - if (campo.includes('correoElectronico')) campoTraducido = 'correo electrónico'; - else if (campo.includes('telefono')) campoTraducido = 'número de teléfono'; + let campoTraducido = campo; + if (campo.includes('correoElectronico')) campoTraducido = 'correo electrónico'; + else if (campo.includes('telefono')) campoTraducido = 'número de teléfono'; - throw new Error(`La entrada ${campoTraducido} "${valorDuplicado}" esta duplicada`); - } + throw new Error(`La entrada ${campoTraducido} "${valorDuplicado}" esta duplicada`); + } - throw new Error(`Error en importación masiva: ${mensajeOriginal}`); + throw new Error(`Error en importación masiva: ${mensajeOriginal}`); } finally { if (conn) conn.release(); } -}; \ No newline at end of file +}; diff --git a/dump.rdb b/dump.rdb new file mode 100644 index 0000000000000000000000000000000000000000..fa5c9e72c63c7b9e9993b03a8f2f22f7e6099527 GIT binary patch literal 88 zcmWG?b@2=~FfcUw#aWb^l3A=uO-d|IJ;3mPqFuj=r^{9z0GJmcb^rhX literal 0 HcmV?d00001 From dcf5d3498703ef3a32ac9deb815ac17fefb1c027 Mon Sep 17 00:00:00 2001 From: PAOLA MARIA garrido Date: Thu, 29 May 2025 16:14:26 -0600 Subject: [PATCH 033/116] feat: agregar la consulta --- .../repositorioExportarEmpleado.js | 4 +-- Utilidades/Constantes/consultasEmpleados.js | 18 +++++++++++ package-lock.json | 31 +++++++++++++++++++ package.json | 1 + 4 files changed, 52 insertions(+), 2 deletions(-) diff --git a/Empleados/Datos/Repositorios/repositorioExportarEmpleado.js b/Empleados/Datos/Repositorios/repositorioExportarEmpleado.js index 6a4d2dbe..74aae58c 100644 --- a/Empleados/Datos/Repositorios/repositorioExportarEmpleado.js +++ b/Empleados/Datos/Repositorios/repositorioExportarEmpleado.js @@ -1,5 +1,5 @@ const correrQuery = require('@altertex/util/ser/correrQuery'); -const CONSULTAS_EXPORTAR_EMPLEADOS = require('@altertex/util/const/consultasExportarEmpleados'); +const CONSULTAS_EMPLEADOS = require('@altertex/util/const/consultasEmpleados'); /** * Consulta la lista de empleados con todos los datos necesarios para exportar en CSV. @@ -13,6 +13,6 @@ const CONSULTAS_EXPORTAR_EMPLEADOS = require('@altertex/util/const/consultasExpo * @see [RF59 - Documentación de requisitos](https://codeandco-wiki.netlify.app/docs/next/proyectos/textiles/documentacion/requisitos/RF59) */ exports.obtenerEmpleadosExportacion = () => { - const query = CONSULTAS_EXPORTAR_EMPLEADOS.OBTENER_DATOS_EXPORTACION; + const query = CONSULTAS_EMPLEADOS.OBTENER_DATOS_EXPORTACION; return correrQuery(query); }; diff --git a/Utilidades/Constantes/consultasEmpleados.js b/Utilidades/Constantes/consultasEmpleados.js index 38e3b382..d8733a9d 100644 --- a/Utilidades/Constantes/consultasEmpleados.js +++ b/Utilidades/Constantes/consultasEmpleados.js @@ -24,4 +24,22 @@ module.exports = { numeroEmergencia = ?, areaTrabajo = ?, posicion = ?, cantidadPuntos = ?, antiguedad = ? WHERE idEmpleado = ?; `, + OBTENER_DATOS_EXPORTACION: ` + SELECT + e.idEmpleado, + u.nombreCompleto, + u.correoElectronico, + u.numeroTelefono, + u.direccion, + u.fechaNacimiento, + u.genero, + u.estatus, + e.numeroEmergencia, + e.areaTrabajo, + e.posicion, + e.cantidadPuntos, + e.antiguedad + FROM empleado e + JOIN usuario u ON e.idUsuario = u.idUsuario; + `, }; diff --git a/package-lock.json b/package-lock.json index 23c6c96a..5d42a497 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "express": "^4.21.2", "helmet": "^8.1.0", "jest": "^29.7.0", + "json2csv": "^6.0.0-alpha.2", "jsonwebtoken": "^9.0.2", "module-alias": "^2.2.3", "multer": "^1.4.5-lts.1", @@ -3840,6 +3841,11 @@ "node": ">=18.0.0" } }, + "node_modules/@streamparser/json": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@streamparser/json/-/json-0.0.6.tgz", + "integrity": "sha512-vL9EVn/v+OhZ+Wcs6O4iKE9EUpwHUqHmCtNUMWjqp+6dr85+XPOSGTEsqYNq1Vn04uk9SWlOVmx9J48ggJVT2Q==" + }, "node_modules/@swagger-api/apidom-ast": { "version": "1.0.0-beta.37", "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ast/-/apidom-ast-1.0.0-beta.37.tgz", @@ -8388,6 +8394,31 @@ "license": "ISC", "optional": true }, + "node_modules/json2csv": { + "version": "6.0.0-alpha.2", + "resolved": "https://registry.npmjs.org/json2csv/-/json2csv-6.0.0-alpha.2.tgz", + "integrity": "sha512-nJ3oP6QxN8z69IT1HmrJdfVxhU1kLTBVgMfRnNZc37YEY+jZ4nU27rBGxT4vaqM/KUCavLRhntmTuBFqZLBUcA==", + "dependencies": { + "@streamparser/json": "^0.0.6", + "commander": "^6.2.0", + "lodash.get": "^4.4.2" + }, + "bin": { + "json2csv": "bin/json2csv.js" + }, + "engines": { + "node": ">= 12", + "npm": ">= 6.13.0" + } + }, + "node_modules/json2csv/node_modules/commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "engines": { + "node": ">= 6" + } + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", diff --git a/package.json b/package.json index a503511e..ce3f9c08 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "express": "^4.21.2", "helmet": "^8.1.0", "jest": "^29.7.0", + "json2csv": "^6.0.0-alpha.2", "jsonwebtoken": "^9.0.2", "module-alias": "^2.2.3", "multer": "^1.4.5-lts.1", From 2d724968ef77e29b9a286ff284670e9d37af2b46 Mon Sep 17 00:00:00 2001 From: max Date: Thu, 29 May 2025 16:20:37 -0600 Subject: [PATCH 034/116] feat(usuarios): implement update user functionality with controller, repository, and routes --- .../actualizarUsuario.controller.js | 49 +++++++++++++++++++ .../repositorioActualizarUsuario.js | 40 +++++++++++++++ .../actualizarUsuario.routes.js | 0 Usuarios/Rutas/indexUsuarios.routes.js | 1 + Utilidades/Constantes/consultasUsuarios.js | 10 +++- Utilidades/Constantes/mensajesUsuarios.js | 8 +++ 6 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 Usuarios/Controladores/actualizarUsuario.controller.js create mode 100644 Usuarios/Datos/Repositorios/repositorioActualizarUsuario.js create mode 100644 Usuarios/Rutas/RutasIndividuales/actualizarUsuario.routes.js diff --git a/Usuarios/Controladores/actualizarUsuario.controller.js b/Usuarios/Controladores/actualizarUsuario.controller.js new file mode 100644 index 00000000..8569655c --- /dev/null +++ b/Usuarios/Controladores/actualizarUsuario.controller.js @@ -0,0 +1,49 @@ +const MENSAJES = require('@altertex/util/const/mensajesUsuarios'); +const repositorio = require('@altertex/emp/repos/repositorioActualizarUsuario'); +//RF[4] Actualizar Usuario - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF4] +/** + * Controlador para actualizar la información de un usuario. + * + * Este endpoint recibe un objeto con los cambios que se aplicarán + * sobre un usuario y usa su repositorio para hacer el cambio en la + * base de datos. + * + * @function actualizarUsuario + * @async + * @param {Express.Request} req - Objeto de solicitud HTTP de Express. + * @param {object} req.body - Cuerpo de la solicitud. + * @param {object|Array} req.body.cambios - Información del usuario a actualizar. + * @param {Express.Response} res - Objecto de respuesta HTTP de Express. + * @returns {Promise} Retorna una respuesta JSON indicando éxito o un error. + */ +exports.actualizarUsuario = async (req, res) => { + let datos; + + // Si no hay cambios + if (req.body.id || req.body.idUsuario) { + datos = [req.body]; + } else if (req.body.cambios) { + // Si la información viene en el formato esperado (hay cambios) + datos = Array.isArray(req.body.cambios) ? req.body.cambios : [req.body.cambios]; + } else { + return res + .status(MENSAJES.ERROR_ACTUALIZAR_USUARIO.codigo) + .json({ mensaje: MENSAJES.ERROR_ACTUALIZAR_USUARIO.mensaje }); + } +}; + +if (!datos || datos.length === 0) { + return res + .status(MENSAJES.ERROR_ACTUALIZAR_USUARIO.codigo) + .json({ mensaje: MENSAJES.ERROR_ACTUALIZAR_USUARIO.mensaje }); +} +try { + await repositorio.actualizarUsuario(datos); + return res + .status(MENSAJES.USUARIO_ACTUALIZADO.codigo) + .json({ mensaje: MENSAJES.USUARIO_ACTUALIZADO.mensaje, datos }); +} catch { + return res + .status(MENSAJES.ERROR_ACTUALIZAR_USUARIO.codigo) + .json({ mensaje: MENSAJES.ERROR_ACTUALIZAR_USUARIO.mensaje }); +} diff --git a/Usuarios/Datos/Repositorios/repositorioActualizarUsuario.js b/Usuarios/Datos/Repositorios/repositorioActualizarUsuario.js new file mode 100644 index 00000000..c4854ef8 --- /dev/null +++ b/Usuarios/Datos/Repositorios/repositorioActualizarUsuario.js @@ -0,0 +1,40 @@ +const correrQuery = require('@altertex/util/ser/correrQuery'); +const MENSAJES = require('@altertex/util/const/mensajesUsuarios'); +const CONSULTAS_USUARIOS = require('@altertex/util/const/consultasUsuarios'); + +//RF[4] Actualizar Usuario - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF4 +/** + * Repositorio para actualizar los datos de un usuario en la BD. + * + * Recorre un arreglo de objetos que contienen la información de cada usuario para + * actualizar su información. + * + * Utiliza un array de objetos con la información del usuario, y hace la + * consulta correspondiente a la base de datos. + * + * @function actualizarUsuario + * @async + * @param {Array<{ idUsuario: number, nombreCompleto: string, correoElectronico: string, telefono: string }>} datos - Lista de + * información del usuario a actualizar. + * @throws {Error} Si el arreglo está vacío o si ocurre un error en la base de datos. + * @returns {Promise} Promesa que se resuelve cuando todas las actualizaciones han sido ejecutadas. + */ +exports.actualizarUsuario = async (datos) => { + if (!Array.isArray(datos) || datos.length === 0) { + throw new Error('Sin datos para actualizar.'); + } + try { + await Promise.all( + datos.map(({ idUsuario, nombreCompleto, correoElectronico, telefono }) => { + return correrQuery(CONSULTAS_USUARIOS.ACTUALIZAR, [ + nombreCompleto, + correoElectronico, + telefono, + idUsuario, + ]); + }) + ); + } catch { + throw new Error(MENSAJES.ERROR_ACTUALIZAR_USUARIO.mensaje); + } +}; diff --git a/Usuarios/Rutas/RutasIndividuales/actualizarUsuario.routes.js b/Usuarios/Rutas/RutasIndividuales/actualizarUsuario.routes.js new file mode 100644 index 00000000..e69de29b diff --git a/Usuarios/Rutas/indexUsuarios.routes.js b/Usuarios/Rutas/indexUsuarios.routes.js index 5dc5ed19..9926a904 100644 --- a/Usuarios/Rutas/indexUsuarios.routes.js +++ b/Usuarios/Rutas/indexUsuarios.routes.js @@ -4,6 +4,7 @@ const rutasCrearUsuario = require('@altertex/usu/rutasInd/crearUsuario.routes'); const rutasLeerUsuario = require('@altertex/usu/rutasInd/leerUsuario.routes'); const rutasConsultarListaUsuarios = require('@altertex/usu/rutasInd/consultarListaUsuarios.routes'); const rutasEliminarUsuario = require('@altertex/usu/rutasInd/eliminarUsuario.routes'); +const rutasActualizarUsuario = require('@altertex/usu/rutasInd/actualizarUsuario.routes'); const RUTAS = require('@altertex/util/const/rutas'); diff --git a/Utilidades/Constantes/consultasUsuarios.js b/Utilidades/Constantes/consultasUsuarios.js index c0668c4c..7768c4a6 100644 --- a/Utilidades/Constantes/consultasUsuarios.js +++ b/Utilidades/Constantes/consultasUsuarios.js @@ -93,6 +93,14 @@ module.exports = { DELETE FROM usuario WHERE idUsuario = (?); `, + + ACTUALIZAR: ` + UPDATE usuario SET + nombreCompleto = ?, correoElectronico = ?, estatus = ? + , + cantidadPuntos = ?, antiguedad = ? WHERE idUsuario = ?; + `, + VALIDAR_CORREO: ` SELECT idUsuario FROM usuario @@ -176,6 +184,4 @@ module.exports = { WHERE idUsuario IN (?) AND puedeActivar2FA = true; `, - - }; diff --git a/Utilidades/Constantes/mensajesUsuarios.js b/Utilidades/Constantes/mensajesUsuarios.js index ac2ddeee..2869d7cd 100644 --- a/Utilidades/Constantes/mensajesUsuarios.js +++ b/Utilidades/Constantes/mensajesUsuarios.js @@ -14,6 +14,10 @@ module.exports = { codigo: 200, mensaje: 'Lista de usuarios obtenida exitosamente.', }, + USUARIO_ACTUALIZADO: { + codigo: 200, + mensaje: 'Usuario actualizado correctamente.', + }, // 204 - Sin contenido USUARIOS_NO_ENCONTRADOS: { @@ -87,4 +91,8 @@ module.exports = { codigo: 500, mensaje: 'Ocurrió un error al intentar eliminar el usuario.', }, + ERROR_ACTUALIZAR_USUARIO: { + codigo: 500, + mensaje: 'Ocurrió un error al intentar actualizar el usuario.', + }, }; From fc9a87f65512208cd341a30444a06286e7bac740 Mon Sep 17 00:00:00 2001 From: Diego Alfaro Pinto Date: Mon, 26 May 2025 17:21:01 -0600 Subject: [PATCH 035/116] feat: agregar funcionalidad para leer rol --- Roles/Controladores/consultarDetalleRol.js | 69 +++++++++++++++++++ .../Repositorios/repositorioDetalleRol.js | 29 ++++++++ .../consultarDetalleRol.routes.js | 54 +++++++++++++++ Roles/Rutas/indexRoles.routes.js | 6 +- Utilidades/Constantes/consultasRoles.js | 56 ++++++++++----- Utilidades/Constantes/rutas.js | 5 +- 6 files changed, 198 insertions(+), 21 deletions(-) create mode 100644 Roles/Controladores/consultarDetalleRol.js create mode 100644 Roles/Datos/Repositorios/repositorioDetalleRol.js create mode 100644 Roles/Rutas/RutasIndividuales/consultarDetalleRol.routes.js diff --git a/Roles/Controladores/consultarDetalleRol.js b/Roles/Controladores/consultarDetalleRol.js new file mode 100644 index 00000000..a402fe08 --- /dev/null +++ b/Roles/Controladores/consultarDetalleRol.js @@ -0,0 +1,69 @@ +const repositorio = require('@altertex/rol/repos/repositorioDetalleRol'); +const MENSAJES_ROLES = require('@altertex/util/const/mensajesRoles'); + +/** + * RF8 - Leer rol + * Documentación del requisito funcional: + * https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF8 + * + * @function consultarDetalle + * @async + * @param {object} req - Objeto de solicitud HTTP (Request). + * @param {object} res - Objeto de respuesta HTTP (Response). + * @returns {Response} Respuesta HTTP con los detalles del rol solicitado. + * + * @description + * Este controlador obtiene el detalle de un rol: nombre, descripción, + * número de usuarios asociados y permisos relacionados. + */ +exports.consultarDetalle = async (req, res) => { + try { + const { idRol } = req.query; + + // Validación: verificar que se proporcione un ID válido + if (!idRol || isNaN(Number(idRol))) { + return res + .status(MENSAJES_ROLES.PARAMETROS_INVALIDOS.codigo) + .json({ mensaje: MENSAJES_ROLES.PARAMETROS_INVALIDOS.mensaje }); + } + + // Se consulta al repositorio de roles para obtener el detalle por ID. + const resultado = await repositorio.obtenerDetalleRol(Number(idRol)); + + // Validación: si no se encontró el rol, se responde con mensaje de "sin resultados". + if (!resultado || resultado.length === 0) { + return res + .status(MENSAJES_ROLES.SIN_RESULTADOS.codigo) + .json({ mensaje: MENSAJES_ROLES.SIN_RESULTADOS.mensaje }); + } + + // Extrae los datos generales del rol desde la primera fila + const { nombreRol, descripcionRol, totalUsuarios } = resultado[0]; + + // Construye arreglo de permisos (omite si no tiene permisos) + const permisos = resultado + .filter(permiso => permiso.idPermiso !== null) + .map(permiso => ({ + id: permiso.idPermiso, + nombre: permiso.nombrePermiso, + descripcion: permiso.descripcionPermiso, + })); + + // Respuesta exitosa con datos + return res.status(MENSAJES_ROLES.CONSULTA_EXITOSA.codigo).json({ + mensaje: MENSAJES_ROLES.CONSULTA_EXITOSA.mensaje, + rol: { + idRol: Number(idRol), + nombre: nombreRol, + descripcion: descripcionRol, + totalUsuarios, + permisos, + }, + }); + } catch { + // Error inesperado en el servidor + return res + .status(MENSAJES_ROLES.ERROR_CONSULTAR_ROLES.codigo) + .json({ mensaje: MENSAJES_ROLES.ERROR_CONSULTAR_ROLES.mensaje }); + } +}; \ No newline at end of file diff --git a/Roles/Datos/Repositorios/repositorioDetalleRol.js b/Roles/Datos/Repositorios/repositorioDetalleRol.js new file mode 100644 index 00000000..b950287a --- /dev/null +++ b/Roles/Datos/Repositorios/repositorioDetalleRol.js @@ -0,0 +1,29 @@ +const correrQuery = require('@altertex/util/ser/correrQuery'); +const CONSULTAS_ROLES = require('@altertex/util/const/consultasRoles'); +/** + * RF8 - Leer detalle de un rol + * Documentación del requisito funcional: + * https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF8 + * + * @async + * @function obtenerDetalleRol + * @param {number} idRol - ID del rol que se desea consultar. + * @returns {Promise>} Arreglo de objetos con datos del rol y sus permisos asociados. + * + * @throws {Error} Si ocurre un error en la consulta a la base de datos. + */ +exports.obtenerDetalleRol = async (idRol) => { + const query = CONSULTAS_ROLES.OBTENER_DETALLE_ROL; + + try { + const resultado = await correrQuery(query, [idRol]); + + if (!resultado || resultado.length === 0) { + throw new Error('Rol no encontrado'); + } + + return resultado; + } catch { + throw new Error('Error al consultar el detalle del rol'); + } +}; \ No newline at end of file diff --git a/Roles/Rutas/RutasIndividuales/consultarDetalleRol.routes.js b/Roles/Rutas/RutasIndividuales/consultarDetalleRol.routes.js new file mode 100644 index 00000000..027bbac2 --- /dev/null +++ b/Roles/Rutas/RutasIndividuales/consultarDetalleRol.routes.js @@ -0,0 +1,54 @@ +const express = require('express'); +const ruteador = express.Router(); + +const controlador = require('@altertex/rol/ctrl/consultarDetalleRol.controller'); +const revisarApiKey = require('@altertex/util/inter/revisarApiKey'); +const autorizarToken = require('@altertex/util/inter/autorizarToken'); +const verificarPermisos = require('@altertex/util/inter/verificarPermisos'); +const validarYSanitizar = require('@altertex/util/inter/validarYSanitizar'); +const limitePeticionesDiarias = require('@altertex/util/inter/limitePeticiones'); + +const PERMISOS = require('@altertex/util/const/permisos'); +const RUTAS = require('@altertex/util/const/rutas'); + +/** + * RF## - Leer detalle de un rol - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF## + */ + +/** + * @swagger + * /api/roles/leer: + * get: + * summary: Obtener detalle de un rol + * description: Devuelve nombre, descripción, cantidad de usuarios y permisos de un rol específico. + * tags: [Roles] + * security: + * - ApiKeyAuth: [] + * parameters: + * - in: query + * name: idRol + * required: true + * schema: + * type: integer + * description: ID del rol a consultar. + * responses: + * 200: + * description: Detalle del rol obtenido exitosamente. + * 400: + * description: Parámetros inválidos. + * 404: + * description: Rol no encontrado. + * 500: + * description: Error interno del servidor. + */ +ruteador.get( + RUTAS.ROLES.LEER_ROL, + validarYSanitizar, + revisarApiKey(), + autorizarToken, + limitePeticionesDiarias, + verificarPermisos(PERMISOS.LEER_ROL), + controlador.consultarDetalle, +); + +module.exports = ruteador; \ No newline at end of file diff --git a/Roles/Rutas/indexRoles.routes.js b/Roles/Rutas/indexRoles.routes.js index 6ea28210..1fa63569 100644 --- a/Roles/Rutas/indexRoles.routes.js +++ b/Roles/Rutas/indexRoles.routes.js @@ -21,6 +21,8 @@ const rutasObtenerOpcionesRol = require('@altertex/rol/rutasInd/obtenerOpcionesR const rutasEliminarRol = require('@altertex/rol/rutasInd/eliminarRol.routes'); +const rutasConsultarDetalle = require('@altertex/rol/rutasInd/consultarDetalleRol.routes'); + // Importación del archivo de constantes donde están definidas las rutas base del sistema. const RUTAS = require('@altertex/util/const/rutas'); @@ -39,5 +41,7 @@ ruteador.use(RUTAS.ROLES.BASE, rutasObtenerOpcionesRol); ruteador.use(RUTAS.ROLES.BASE, rutasEliminarRol); +ruteador.use(RUTAS.ROLES.BASE, rutasConsultarDetalle); + // Exporta el enrutador para ser utilizado en el archivo principal de rutas de la aplicación (por ejemplo: app.js). -module.exports = ruteador; +module.exports = ruteador; \ No newline at end of file diff --git a/Utilidades/Constantes/consultasRoles.js b/Utilidades/Constantes/consultasRoles.js index 01d1fc37..d4f72f9c 100644 --- a/Utilidades/Constantes/consultasRoles.js +++ b/Utilidades/Constantes/consultasRoles.js @@ -28,38 +28,58 @@ module.exports = { * Agrupa los resultados por `idRol` para consolidar la información por rol. */ OBTENER_LISTA: ` - SELECT r.idRol, r.nombre, r.descripcion, COUNT(ur.idUsuario) AS totalUsuarios - FROM rol r - LEFT JOIN usuario_rol ur ON r.idRol = ur.idRol - GROUP BY r.idRol; + SELECT r.idRol, r.nombre, r.descripcion, COUNT(ur.idUsuario) AS totalUsuarios + FROM rol r + LEFT JOIN usuario_rol ur ON r.idRol = ur.idRol + GROUP BY r.idRol; `, VERIFICAR_NOMBRE_ROL: ` - SELECT idRol FROM rol WHERE nombre = ? LIMIT 1`, + SELECT idRol + FROM rol + WHERE nombre = ? LIMIT 1`, VERIFICAR_PERMISO: ` - SELECT idPermiso FROM permiso WHERE idPermiso = ? LIMIT 1`, + SELECT idPermiso + FROM permiso + WHERE idPermiso = ? LIMIT 1`, INSERTAR_ROL: ` - INSERT INTO rol (nombre, descripcion) - VALUES (?, ?)`, + INSERT INTO rol (nombre, descripcion) + VALUES (?, ?)`, INSERTAR_ROL_PERMISO: ` - INSERT INTO rol_permiso (idRol, idPermiso) - VALUES (?, ?)`, + INSERT INTO rol_permiso (idRol, idPermiso) + VALUES (?, ?)`, OBTENER_PERMISOS_POR_CLIENTE: ` - SELECT idPermiso AS id, nombre FROM permiso; + SELECT idPermiso AS id, nombre + FROM permiso; `, ELIMINAR_ROL: ` - DELETE FROM rol - WHERE idRol IN (__IDS__); + DELETE + FROM rol + WHERE idRol IN (__IDS__); `, VALIDAR_ROL_SIN_USUARIOS: ` - SELECT COUNT(*) AS cantidad - FROM usuario_rol - WHERE idRol IN (__IDS__); + SELECT COUNT(*) AS cantidad + FROM usuario_rol + WHERE idRol IN (__IDS__); + `, + OBTENER_DETALLE_ROL: ` + SELECT r.idRol, + r.nombre AS nombreRol, + r.descripcion AS descripcionRol, + (SELECT COUNT(*) + FROM usuario_rol ur + WHERE ur.idRol = r.idRol) AS totalUsuarios, + p.idPermiso, + p.nombre AS nombrePermiso, + p.descripcion AS descripcionPermiso + FROM rol r + LEFT JOIN rol_permiso rp ON r.idRol = rp.idRol + LEFT JOIN permiso p ON rp.idPermiso = p.idPermiso + WHERE r.idRol = ?; `, - -}; +}; \ No newline at end of file diff --git a/Utilidades/Constantes/rutas.js b/Utilidades/Constantes/rutas.js index 92946212..8c45804c 100644 --- a/Utilidades/Constantes/rutas.js +++ b/Utilidades/Constantes/rutas.js @@ -8,7 +8,7 @@ module.exports = { CERRAR_SESION: '/cerrar-sesion', USUARIO_AUTENTICADO: '/autenticar', ACTIVAR_2FA: '/activar-2fa', - VERIFICAR_2FA: '/verificar-2fa' + VERIFICAR_2FA: '/verificar-2fa', }, USUARIOS: { BASE: '/usuarios', @@ -87,6 +87,7 @@ module.exports = { OBTENER_OPCIONES: '/obtener-opciones', CONFIRMAR_CREACION: '/confirmar-creacion', ELIMINAR_ROL: '/eliminar', + LEER_ROL: '/leer', }, PEDIDOS: { BASE: '/pedidos', @@ -99,4 +100,4 @@ module.exports = { ACTUALIZAR: '/actualizar', }, API_DOCS: '/api-docs', -}; +}; \ No newline at end of file From 89181417ee94bed52c8fac49d4a08bae67a4d1ea Mon Sep 17 00:00:00 2001 From: Hiram <147564077+Hiram10tec@users.noreply.github.com> Date: Mon, 26 May 2025 18:36:57 -0600 Subject: [PATCH 036/116] fix(rol): cambio de nombre archivo de controller --- .../{consultarDetalleRol.js => consultarDetalleRol.controller.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Roles/Controladores/{consultarDetalleRol.js => consultarDetalleRol.controller.js} (100%) diff --git a/Roles/Controladores/consultarDetalleRol.js b/Roles/Controladores/consultarDetalleRol.controller.js similarity index 100% rename from Roles/Controladores/consultarDetalleRol.js rename to Roles/Controladores/consultarDetalleRol.controller.js From de7321f47d5dbcaca3056f47c5954054d19eea47 Mon Sep 17 00:00:00 2001 From: Hiram <147564077+Hiram10tec@users.noreply.github.com> Date: Tue, 27 May 2025 10:49:28 -0600 Subject: [PATCH 037/116] Feature(categorias):Leer-categoria --- .../consultarDetalleCategoria.controller.js | 41 +++++++++++++++++++ .../repositorioLeerDetalleCategoria.js | 35 ++++++++++++++++ .../consultarDetalleCategoria.routes.js | 22 ++++++++++ Categorias/Rutas/indexCategorias.routes.js | 2 + Utilidades/Constantes/consultasCategorias.js | 13 ++++++ Utilidades/Constantes/rutas.js | 2 +- 6 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 Categorias/Controladores/consultarDetalleCategoria.controller.js create mode 100644 Categorias/Datos/Repositorios/repositorioLeerDetalleCategoria.js create mode 100644 Categorias/Rutas/RutasIndividuales/consultarDetalleCategoria.routes.js diff --git a/Categorias/Controladores/consultarDetalleCategoria.controller.js b/Categorias/Controladores/consultarDetalleCategoria.controller.js new file mode 100644 index 00000000..1bcd4163 --- /dev/null +++ b/Categorias/Controladores/consultarDetalleCategoria.controller.js @@ -0,0 +1,41 @@ +const repositorio = require('@altertex/cat/repos/repositorioLeerDetalleCategoria'); +const MENSAJES_CATEGORIAS = require('@altertex/util/const/mensajesCategorias'); + +/** + * Consulta el detalle de una categoría de productos, incluyendo sus productos asociados. + * + * @function + * @async + * @param {Express.Request} req - Objeto de solicitud HTTP con `req.params.idCategoria`. + * @param {Express.Response} res - Objeto de respuesta HTTP para enviar el resultado. + * + * @returns {Promise} Devuelve una respuesta HTTP con el detalle de la categoría o un mensaje de error. + * + * @description + * Implementa el RF48: Leer categoría de productos. + * Si no se encuentra la categoría, devuelve código 404. + * Si ocurre un error inesperado, devuelve código 500. + * + * @see [RF48 - Documentación de requisitos](https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF48) + */ +exports.consultarDetalleCategoria = async (req, res) => { + const idCategoria = parseInt(req.params.idCategoria); + + try { + const resultado = await repositorio.leerDetalleCategoria(idCategoria); + + if (!resultado) { + return res + .status(MENSAJES_CATEGORIAS.CATEGORIA_NO_ENCONTRADA.codigo) + .json({ mensaje: MENSAJES_CATEGORIAS.CATEGORIA_NO_ENCONTRADA.mensaje }); + } + + return res + .status(MENSAJES_CATEGORIAS.CATEGORIA_OBTENIDA.codigo) + .json({ mensaje: MENSAJES_CATEGORIAS.CATEGORIA_OBTENIDA.mensaje, categoria: resultado }); + } catch { + return res + .status(MENSAJES_CATEGORIAS.ERROR_OBTENER_CATEGORIA.codigo) + .json({ mensaje: MENSAJES_CATEGORIAS.ERROR_OBTENER_CATEGORIA.mensaje }); + } +}; \ No newline at end of file diff --git a/Categorias/Datos/Repositorios/repositorioLeerDetalleCategoria.js b/Categorias/Datos/Repositorios/repositorioLeerDetalleCategoria.js new file mode 100644 index 00000000..63b0ca0e --- /dev/null +++ b/Categorias/Datos/Repositorios/repositorioLeerDetalleCategoria.js @@ -0,0 +1,35 @@ +const correrQuery = require('@altertex/util/ser/correrQuery'); +const CONSULTAS = require('@altertex/util/const/consultasCategorias'); + +/** + * Consulta el detalle de una categoría y sus productos asociados. + * + * @param {number} idCategoria - ID de la categoría a consultar. + * @returns {Promise} Objeto con la información de la categoría y sus productos, o null si no existe. + * + * @throws {Error} Si ocurre un error al ejecutar la consulta. + * + * @see [RF48 - Documentación de requisitos](https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF48) + */ +exports.leerDetalleCategoria = async (idCategoria) => { + const query = CONSULTAS.LEER_DETALLE_CATEGORIA; + const resultados = await correrQuery(query, [idCategoria]); + + if (!resultados || resultados.length === 0) return null; + + const { nombreCategoria, descripcion } = resultados[0]; + + const productos = resultados + .filter(resul => resul.idProducto !== null) + .map(produc => ({ + idProducto: produc.idProducto, + nombreComun: produc.nombreComun, + })); + + return { + idCategoria, + nombreCategoria, + descripcion, + productos, + }; +}; \ No newline at end of file diff --git a/Categorias/Rutas/RutasIndividuales/consultarDetalleCategoria.routes.js b/Categorias/Rutas/RutasIndividuales/consultarDetalleCategoria.routes.js new file mode 100644 index 00000000..0b755eaf --- /dev/null +++ b/Categorias/Rutas/RutasIndividuales/consultarDetalleCategoria.routes.js @@ -0,0 +1,22 @@ +/** + * RF48 Leer categoría de productos - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF48 + */ + +const express = require('express'); +const ruteador = express.Router(); +const controlador = require('@altertex/cat/ctrl/consultarDetalleCategoria.controller'); +const revisarApiKey = require('@altertex/util/inter/revisarApiKey'); +const autorizarToken = require('@altertex/util/inter/autorizarToken'); +const verificarPermisos = require('@altertex/util/inter/verificarPermisos'); +const PERMISOS = require('@altertex/util/const/permisos'); +const RUTAS = require('@altertex/util/const/rutas'); + +ruteador.get( + `${RUTAS.CATEGORIAS.LEER}/:idCategoria`, + revisarApiKey(), + autorizarToken, + verificarPermisos(PERMISOS.LEER_CATEGORIA_PRODUCTOS), + controlador.consultarDetalleCategoria +); + +module.exports = ruteador; \ No newline at end of file diff --git a/Categorias/Rutas/indexCategorias.routes.js b/Categorias/Rutas/indexCategorias.routes.js index 5555b343..35f1bef5 100644 --- a/Categorias/Rutas/indexCategorias.routes.js +++ b/Categorias/Rutas/indexCategorias.routes.js @@ -3,11 +3,13 @@ const ruteador = express.Router(); const rutasConsultarListaCategorias = require('@altertex/cat/rutasInd/consultarListaCategorias.routes'); const rutasCrearCategoria = require('@altertex/cat/rutasInd/crearCategoria.routes'); const rutasEliminarCategoria = require('@altertex/cat/rutasInd/eliminarCategoria.routes'); +const rutasLeerCategoria = require('@altertex/cat/rutasInd/consultarDetalleCategoria.routes'); const RUTAS = require('@altertex/util/const/rutas'); ruteador.use(RUTAS.CATEGORIAS.BASE, rutasConsultarListaCategorias); ruteador.use(RUTAS.CATEGORIAS.BASE, rutasCrearCategoria); ruteador.use(RUTAS.CATEGORIAS.BASE, rutasEliminarCategoria); +ruteador.use(RUTAS.CATEGORIAS.BASE, rutasLeerCategoria); module.exports = ruteador; diff --git a/Utilidades/Constantes/consultasCategorias.js b/Utilidades/Constantes/consultasCategorias.js index 6890d77a..7715e286 100644 --- a/Utilidades/Constantes/consultasCategorias.js +++ b/Utilidades/Constantes/consultasCategorias.js @@ -47,4 +47,17 @@ module.exports = { FROM producto WHERE idProducto IN (?); `, + + LEER_DETALLE_CATEGORIA: ` + SELECT + c.idCategoria, + c.nombreCategoria, + c.descripcion, + p.idProducto, + p.nombreComun + FROM categoria c + LEFT JOIN categoria_producto cp ON c.idCategoria = cp.idCategoria + LEFT JOIN producto p ON cp.idProducto = p.idProducto + WHERE c.idCategoria = ?; + `, }; diff --git a/Utilidades/Constantes/rutas.js b/Utilidades/Constantes/rutas.js index 8c45804c..11d9254e 100644 --- a/Utilidades/Constantes/rutas.js +++ b/Utilidades/Constantes/rutas.js @@ -25,7 +25,7 @@ module.exports = { CONSULTAR_LISTA_USUARIOS: '/consultar-lista-usuarios', CREAR: '/crear', ELIMINAR_USUARIOS: '/eliminar-usuarios', - LEER: '/consultar-usuario', + LEER: '/leer', }, EVENTOS: { BASE: '/eventos', From f9a6c8533e1e7fc6c844aa0fd7d7a44accee9fe7 Mon Sep 17 00:00:00 2001 From: Hiram <147564077+Hiram10tec@users.noreply.github.com> Date: Wed, 28 May 2025 11:14:25 -0600 Subject: [PATCH 038/116] =?UTF-8?q?feat(Categor=C3=ADas):=20actualizar=20c?= =?UTF-8?q?ategor=C3=ADa?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../actualizarCategoria.controller.js | 33 +++++++++++++++++++ .../repositorioActualizarCategorias.js | 22 +++++++++++++ .../actualizarCategorias.routes.js | 22 +++++++++++++ Categorias/Rutas/indexCategorias.routes.js | 2 ++ Utilidades/Constantes/consultasCategorias.js | 17 +++++++++- Utilidades/Constantes/rutas.js | 1 + 6 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 Categorias/Controladores/actualizarCategoria.controller.js create mode 100644 Categorias/Datos/Repositorios/repositorioActualizarCategorias.js create mode 100644 Categorias/Rutas/RutasIndividuales/actualizarCategorias.routes.js diff --git a/Categorias/Controladores/actualizarCategoria.controller.js b/Categorias/Controladores/actualizarCategoria.controller.js new file mode 100644 index 00000000..295391d7 --- /dev/null +++ b/Categorias/Controladores/actualizarCategoria.controller.js @@ -0,0 +1,33 @@ +const { actualizarCategoria } = require('@altertex/cat/repos/repositorioActualizarCategorias'); +const MENSAJES = require('@altertex/util/const/mensajesCategorias'); + +/** + * RF49 - Actualizar categoría de productos - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF49 + * + * @param {import('express').Request} req + * @param {import('express').Response} res + * @returns {Promise} + */ +exports.actualizarCategoria = async (req, res) => { + try { + const { idCategoria } = req.params; + const { nombreCategoria, descripcion, productos } = req.body; + + if (!idCategoria || !nombreCategoria || typeof nombreCategoria !== 'string') { + return res.status(400).json(MENSAJES.PARAMETROS_INVALIDOS); + } + + await actualizarCategoria({ idCategoria, nombreCategoria, descripcion, productos }); + + return res.status(200).json({ + codigo: 200, + mensaje: 'Categoría actualizada correctamente.', + }); + } catch (error) { + console.error('Error al actualizar categoría:', error); + return res.status(500).json({ + codigo: 500, + mensaje: 'Ocurrió un error al actualizar la categoría.', + }); + } +}; \ No newline at end of file diff --git a/Categorias/Datos/Repositorios/repositorioActualizarCategorias.js b/Categorias/Datos/Repositorios/repositorioActualizarCategorias.js new file mode 100644 index 00000000..b3c72b05 --- /dev/null +++ b/Categorias/Datos/Repositorios/repositorioActualizarCategorias.js @@ -0,0 +1,22 @@ +const correrQuery = require('@altertex/util/ser/correrQuery'); +const CONSULTAS = require('@altertex/util/const/consultasCategorias'); + +/** + * Actualiza el nombre, descripción y productos de una categoría. + * + * @param {object} categoria - Objeto con los datos de la categoría. + * @param {number} categoria.idCategoria - ID de la categoría a actualizar. + * @param {string} categoria.nombreCategoria - Nuevo nombre de la categoría. + * @param {string} categoria.descripcion - Nueva descripción. + * @param {number[]} categoria.productos - IDs de productos asociados. + * @returns {Promise} + */ +exports.actualizarCategoria = async ({ idCategoria, nombreCategoria, descripcion, productos }) => { + await correrQuery(CONSULTAS.ACTUALIZAR_CATEGORIA, [nombreCategoria, descripcion, idCategoria]); + await correrQuery(CONSULTAS.ELIMINAR_PRODUCTOS_CATEGORIA, [idCategoria]); + + if (productos && productos.length > 0) { + const valores = productos.map((idProd) => [idCategoria, idProd]); + await correrQuery(CONSULTAS.ASIGNAR_PRODUCTOS_A_CATEGORIA, [valores]); + } +}; \ No newline at end of file diff --git a/Categorias/Rutas/RutasIndividuales/actualizarCategorias.routes.js b/Categorias/Rutas/RutasIndividuales/actualizarCategorias.routes.js new file mode 100644 index 00000000..34a74260 --- /dev/null +++ b/Categorias/Rutas/RutasIndividuales/actualizarCategorias.routes.js @@ -0,0 +1,22 @@ +/** + * RF49 - Actualizar categoría de productos - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF49 + */ + +const express = require('express'); +const ruteador = express.Router(); +const controlador = require('@altertex/cat/ctrl/actualizarCategoria.controller'); +const revisarApiKey = require('@altertex/util/inter/revisarApiKey'); +const autorizarToken = require('@altertex/util/inter/autorizarToken'); +const verificarPermisos = require('@altertex/util/inter/verificarPermisos'); +const PERMISOS = require('@altertex/util/const/permisos'); +const RUTAS = require('@altertex/util/const/rutas'); + +ruteador.put( + `${RUTAS.CATEGORIAS.ACTUALIZAR}/:idCategoria`, + revisarApiKey(), + autorizarToken, + verificarPermisos(PERMISOS.ACTUALIZAR_CATEGORIA_PRODUCTOS), + controlador.actualizarCategoria +); + +module.exports = ruteador; \ No newline at end of file diff --git a/Categorias/Rutas/indexCategorias.routes.js b/Categorias/Rutas/indexCategorias.routes.js index 35f1bef5..d412692f 100644 --- a/Categorias/Rutas/indexCategorias.routes.js +++ b/Categorias/Rutas/indexCategorias.routes.js @@ -4,6 +4,7 @@ const rutasConsultarListaCategorias = require('@altertex/cat/rutasInd/consultarL const rutasCrearCategoria = require('@altertex/cat/rutasInd/crearCategoria.routes'); const rutasEliminarCategoria = require('@altertex/cat/rutasInd/eliminarCategoria.routes'); const rutasLeerCategoria = require('@altertex/cat/rutasInd/consultarDetalleCategoria.routes'); +const rutasActualizarCategoria = require('@altertex/cat/rutasInd/actualizarCategorias.routes'); const RUTAS = require('@altertex/util/const/rutas'); @@ -11,5 +12,6 @@ ruteador.use(RUTAS.CATEGORIAS.BASE, rutasConsultarListaCategorias); ruteador.use(RUTAS.CATEGORIAS.BASE, rutasCrearCategoria); ruteador.use(RUTAS.CATEGORIAS.BASE, rutasEliminarCategoria); ruteador.use(RUTAS.CATEGORIAS.BASE, rutasLeerCategoria); +ruteador.use(RUTAS.CATEGORIAS.BASE, rutasActualizarCategoria); module.exports = ruteador; diff --git a/Utilidades/Constantes/consultasCategorias.js b/Utilidades/Constantes/consultasCategorias.js index 7715e286..23acdd34 100644 --- a/Utilidades/Constantes/consultasCategorias.js +++ b/Utilidades/Constantes/consultasCategorias.js @@ -48,7 +48,7 @@ module.exports = { WHERE idProducto IN (?); `, - LEER_DETALLE_CATEGORIA: ` + LEER_DETALLE_CATEGORIA: ` SELECT c.idCategoria, c.nombreCategoria, @@ -60,4 +60,19 @@ module.exports = { LEFT JOIN producto p ON cp.idProducto = p.idProducto WHERE c.idCategoria = ?; `, + + ACTUALIZAR_CATEGORIA: ` + UPDATE categoria + SET nombreCategoria = ?, descripcion = ? + WHERE idCategoria = ?; + `, + + ELIMINAR_PRODUCTOS_CATEGORIA: ` + DELETE FROM categoria_producto WHERE idCategoria = ?; + `, + + ASIGNAR_PRODUCTOS_A_CATEGORIA: ` + INSERT INTO categoria_producto (idCategoria, idProducto) + VALUES ?; + `, }; diff --git a/Utilidades/Constantes/rutas.js b/Utilidades/Constantes/rutas.js index 11d9254e..6b8a774b 100644 --- a/Utilidades/Constantes/rutas.js +++ b/Utilidades/Constantes/rutas.js @@ -26,6 +26,7 @@ module.exports = { CREAR: '/crear', ELIMINAR_USUARIOS: '/eliminar-usuarios', LEER: '/leer', + ACTUALIZAR: '/actualizar', }, EVENTOS: { BASE: '/eventos', From 97dd3eb40afd00a1e6cf48ea0a154a653c2a544b Mon Sep 17 00:00:00 2001 From: angieriosc Date: Fri, 30 May 2025 12:14:37 -0600 Subject: [PATCH 039/116] =?UTF-8?q?Fix:=20cambio=20de=20consulta=20e=20inf?= =?UTF-8?q?ormaci=C3=B3n=20enviada=20de=20grupo=20de=20empleados?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repositorioLeerGrupoDeEmpleados.js | 7 ++++ Empleados/Rutas/indexEmpleados.routes.js | 5 --- .../Constantes/consultasGrupoEmpleados.js | 42 ++++++++++--------- Utilidades/Constantes/rutas.js | 2 +- 4 files changed, 30 insertions(+), 26 deletions(-) diff --git a/Empleados/Datos/Repositorios/repositorioLeerGrupoDeEmpleados.js b/Empleados/Datos/Repositorios/repositorioLeerGrupoDeEmpleados.js index 9ff8be9a..4a4fa87d 100644 --- a/Empleados/Datos/Repositorios/repositorioLeerGrupoDeEmpleados.js +++ b/Empleados/Datos/Repositorios/repositorioLeerGrupoDeEmpleados.js @@ -24,8 +24,15 @@ exports.obtenerGrupoEmpleadosPorId = async (idGrupo) => { nombre: resultado[0].nombre, descripcion: resultado[0].descripcion, setsProductos: resultado[0].setsProductos ? resultado[0].setsProductos.split(', ') : [], + idsSetProductos: resultado[0].idsSetProductos + ? resultado[0].idsSetProductos.split(', ').map(Number) + : [], empleados: resultado[0].infoEmpleados ? resultado[0].infoEmpleados.split(' || ') : [], + idsEmpleados: resultado[0].idsEmpleados + ? resultado[0].idsEmpleados.split(',').map(Number) + : [], }; + return grupoEmpleados; } catch (error) { console.error('Error al obtener el grupo de empleados con id:', error); diff --git a/Empleados/Rutas/indexEmpleados.routes.js b/Empleados/Rutas/indexEmpleados.routes.js index a933a41c..a1f7329d 100644 --- a/Empleados/Rutas/indexEmpleados.routes.js +++ b/Empleados/Rutas/indexEmpleados.routes.js @@ -26,13 +26,8 @@ ruteador.use(RUTAS.EMPLEADOS.BASE, rutasImportarEmpleados); ruteador.use(RUTAS.EMPLEADOS.BASE, rutasLeerGrupoEmpleados); //RF21 - Crear Grupo de Empleados - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF21 ruteador.use(RUTAS.EMPLEADOS.BASE, rutasCrearGrupo); - //RF19 - Actualizar Empleado - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF19 ruteador.use(RUTAS.EMPLEADOS.BASE, rutasActualizarEmpleado); -//RF23 Lee grupo de empleados -https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF23 -ruteador.use(RUTAS.EMPLEADOS.BASE, rutasLeerGrupoEmpleados); -//RF21 - Crear Grupo de Empleados - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF21 -ruteador.use(RUTAS.EMPLEADOS.BASE, rutasCrearGrupo); // RF[24] Actualiza grupo empleado - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF24] ruteador.use(RUTAS.EMPLEADOS.BASE, rutasActualizarEmpleado); diff --git a/Utilidades/Constantes/consultasGrupoEmpleados.js b/Utilidades/Constantes/consultasGrupoEmpleados.js index 946481e2..50c1b91b 100644 --- a/Utilidades/Constantes/consultasGrupoEmpleados.js +++ b/Utilidades/Constantes/consultasGrupoEmpleados.js @@ -20,25 +20,27 @@ module.exports = { DELETE FROM empleado_grupo WHERE idGrupo = ?; `, LEER_GRUPO: ` - SELECT - ge.idGrupo, - ge.nombre AS nombre, - ge.descripcion AS descripcion, - IFNULL(GROUP_CONCAT(DISTINCT sp.nombre SEPARATOR ', '), 'Sin sets de productos asociados') AS setsProductos, - IFNULL(GROUP_CONCAT(DISTINCT CONCAT( - u.nombreCompleto, ' | ', - u.correoElectronico, ' | ', - e.areaTrabajo - ) SEPARATOR ' || '), 'Sin empleados asociados') AS infoEmpleados - FROM grupo_empleado ge - LEFT JOIN empleado_grupo eg ON ge.idGrupo = eg.idGrupo - LEFT JOIN empleado e ON eg.idEmpleado = e.idEmpleado - LEFT JOIN usuario u ON e.idUsuario = u.idUsuario - LEFT JOIN set_producto_grupo_empleado spge ON ge.idGrupo = spge.idGrupo - LEFT JOIN set_producto sp ON spge.idSetProducto = sp.idSetProducto - WHERE ge.idGrupo = ? - GROUP BY ge.idGrupo - ORDER BY ge.idGrupo; + SELECT + ge.idGrupo, + ge.nombre AS nombre, + ge.descripcion AS descripcion, + IFNULL(GROUP_CONCAT(DISTINCT sp.nombre SEPARATOR ', '), 'Sin sets de productos asociados') AS setsProductos, + IFNULL(GROUP_CONCAT(DISTINCT sp.idSetProducto SEPARATOR ','), '') AS idsSetProductos, + IFNULL(GROUP_CONCAT(DISTINCT CONCAT( + u.nombreCompleto, ' | ', + u.correoElectronico, ' | ', + e.areaTrabajo + ) SEPARATOR ' || '), 'Sin empleados asociados') AS infoEmpleados, + IFNULL(GROUP_CONCAT(DISTINCT e.idEmpleado SEPARATOR ','), '') AS idsEmpleados + FROM grupo_empleado ge + LEFT JOIN empleado_grupo eg ON ge.idGrupo = eg.idGrupo + LEFT JOIN empleado e ON eg.idEmpleado = e.idEmpleado + LEFT JOIN usuario u ON e.idUsuario = u.idUsuario + LEFT JOIN set_producto_grupo_empleado spge ON ge.idGrupo = spge.idGrupo + LEFT JOIN set_producto sp ON spge.idSetProducto = sp.idSetProducto + WHERE ge.idGrupo = ? + GROUP BY ge.idGrupo + ORDER BY ge.idGrupo; `, VALIDAR_NOMBRE_REPETIDO: ` @@ -50,7 +52,7 @@ module.exports = { `, ASIGNAR_EMPLEADO_A_GRUPO: ` INSERT INTO empleado_grupo (idEmpleado, idGrupo) VALUES (?, ?); - ` + `, ACTUALIZAR_GRUPO_EMPLEADOS_NOMBRE_DESCRIPCION: ` UPDATE grupo_empleado SET nombre = ?, descripcion = ? diff --git a/Utilidades/Constantes/rutas.js b/Utilidades/Constantes/rutas.js index d81d8df9..766396f8 100644 --- a/Utilidades/Constantes/rutas.js +++ b/Utilidades/Constantes/rutas.js @@ -71,7 +71,7 @@ module.exports = { IMPORTAR_EMPLEADOS: '/importar-empleados', LEER_GRUPO: '/leer-grupo', CREAR_GRUPO: '/crear-grupo', - ACTUALIZAR_GRUPO_EMPLEADO: '/actualizar-grupo', + ACTUALIZAR: '/actualizar-grupo', }, CUOTAS: { BASE: '/cuotas', From 50e7627f64d87ca869eef7bf667f27e1e6be8b96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valeria=20Zu=C3=B1iga=20Mendoza?= Date: Fri, 30 May 2025 17:19:19 -0600 Subject: [PATCH 040/116] feat: Actualizar sets de productos --- .../actualizarSetsProductos.controller.js | 39 +++++++++++++++++++ .../repositorioActualizarSetsProductos.js | 31 +++++++++++++++ .../actualizarSetsProductos.routes.js | 29 ++++++++++++++ .../Rutas/indexSetsProductos.routes.js | 4 ++ .../Constantes/consultasSetsProductos.js | 3 ++ .../Constantes/mensajesSetsProductos.js | 8 ++++ Utilidades/Constantes/rutas.js | 3 +- 7 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 SetsProductos/Controladores/actualizarSetsProductos.controller.js create mode 100644 SetsProductos/Datos/Repositorios/repositorioActualizarSetsProductos.js create mode 100644 SetsProductos/Rutas/RutasIndividuales/actualizarSetsProductos.routes.js diff --git a/SetsProductos/Controladores/actualizarSetsProductos.controller.js b/SetsProductos/Controladores/actualizarSetsProductos.controller.js new file mode 100644 index 00000000..ccf2e14a --- /dev/null +++ b/SetsProductos/Controladores/actualizarSetsProductos.controller.js @@ -0,0 +1,39 @@ +const MENSAJES = require('@altertex/util/const/mensajesSetsProductos'); +const repositorio = require('@altertex/setspro/repos/repositorioActualizarSetsProductos'); +//RF[44] Actualizar set de productos - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF44] + +/** + * Controlador para actualizar la información de un ... + */ +exports.actualizarSetProducto = async (req, res) => { + let datos; + + // Si no hay cambios + if (req.body.id || req.body.idSetProducto) { + datos = [req.body]; + } else if (req.body.cambios) { + // Si la información viene en el formato esperado (hay cambios) + datos = Array.isArray(req.body.cambios) ? req.body.cambios : [req.body.cambios]; + } else { + return res + .status(MENSAJES.ERROR_ACTUALIZAR_SET_PRODUCTOS.codigo) + .json({ mensaje: MENSAJES.ERROR_ACTUALIZAR_SET_PRODUCTOS.mensaje }); + } + + if (!datos || datos.length === 0) { + return res + .status(MENSAJES.ERROR_ACTUALIZAR_SET_PRODUCTOS.codigo) + .json({ mensaje: MENSAJES.ERROR_ACTUALIZAR_SET_PRODUCTOS.mensaje }); + } + + try { + await repositorio.actualizarSetProducto(datos); + return res + .status(MENSAJES.SET_PRODUCTOS_ACTUALIZADO.codigo) + .json({ mensaje: MENSAJES.SET_PRODUCTOS_ACTUALIZADO.mensaje, datos }); + } catch { + return res + .status(MENSAJES.ERROR_ACTUALIZAR_SET_PRODUCTOS.codigo) + .json({ mensaje: MENSAJES.ERROR_ACTUALIZAR_SET_PRODUCTOS.mensaje }); + } +}; diff --git a/SetsProductos/Datos/Repositorios/repositorioActualizarSetsProductos.js b/SetsProductos/Datos/Repositorios/repositorioActualizarSetsProductos.js new file mode 100644 index 00000000..d97c758e --- /dev/null +++ b/SetsProductos/Datos/Repositorios/repositorioActualizarSetsProductos.js @@ -0,0 +1,31 @@ +const correrQuery = require('@altertex/util/ser/correrQuery'); +const MENSAJES = require('@altertex/util/const/mensajesSetsProductos'); +const CONSULTAS_SETS_PRODUCTOS = require('@altertex/util/const/consultasSetsProductos'); + +//RF[44] Actualizar set de productos - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF44] + +/** + *Repositorio para ... + * + */ +exports.actualizarSetProducto = async (datos) => { + if (!Array.isArray(datos) || datos.length === 0) { + throw new Error('Sin datos para actualizar.'); + } + try { + await Promise.all( + datos.map(({ idSetProducto, idCliente, nombre, nombreVisible, descripcion, activo }) => { + return correrQuery(CONSULTAS_SETS_PRODUCTOS.ACTUALIZAR, [ + idCliente, + nombre, + nombreVisible, + descripcion, + activo, + idSetProducto, + ]); + }) + ); + } catch { + throw new Error(MENSAJES.ERROR_ACTUALIZAR_SET_PRODUCTOS.mensaje); + } +}; diff --git a/SetsProductos/Rutas/RutasIndividuales/actualizarSetsProductos.routes.js b/SetsProductos/Rutas/RutasIndividuales/actualizarSetsProductos.routes.js new file mode 100644 index 00000000..1a1a7e31 --- /dev/null +++ b/SetsProductos/Rutas/RutasIndividuales/actualizarSetsProductos.routes.js @@ -0,0 +1,29 @@ +const express = require('express'); +const ruteador = express.Router(); + +const PERMISOS = require('@altertex/util/const/permisos'); +const RUTAS = require('@altertex/util/const/rutas'); +const controlador = require('@altertex/setspro/ctrl/actualizarSetsProductos.controller'); +const revisarApiKey = require('@altertex/util/inter/revisarApiKey'); +const autorizarToken = require('@altertex/util/inter/autorizarToken'); +const revisarPermisos = require('@altertex/util/inter/verificarPermisos'); +const validarYSanitizar = require('@altertex/util/inter/validarYSanitizar'); +const limitePeticionesDiarias = require('@altertex/util/inter/limitePeticiones'); + +//RF[19] Actualizar Empleado - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF19] + +/** + * @swagger... + */ + +ruteador.put( + RUTAS.SETS_PRODUCTOS.ACTUALIZAR, + revisarApiKey(), + validarYSanitizar, + autorizarToken, + limitePeticionesDiarias, + revisarPermisos(PERMISOS.ACTUALIZAR_SET_PRODUCTOS), + controlador.actualizarSetProducto +); + +module.exports = ruteador; diff --git a/SetsProductos/Rutas/indexSetsProductos.routes.js b/SetsProductos/Rutas/indexSetsProductos.routes.js index a30971e5..5e27386f 100644 --- a/SetsProductos/Rutas/indexSetsProductos.routes.js +++ b/SetsProductos/Rutas/indexSetsProductos.routes.js @@ -2,6 +2,7 @@ const express = require('express'); const ruteador = express.Router(); const rutasConsultarSetsProductos = require('@altertex/setspro/rutasInd/consultarSetsProductos.routes'); const rutasEliminarSetsProductos = require('@altertex/setspro/rutasInd/eliminarSetsProductos.routes'); +const rutasActualizarSetsProductos = require('@altertex/setspro/rutasInd/actualizarSetsProductos.routes'); const RUTAS = require('@altertex/util/const/rutas'); @@ -11,4 +12,7 @@ ruteador.use(RUTAS.SETS_PRODUCTOS.BASE, rutasConsultarSetsProductos); //RF[45] Elimina set de productos - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF45] ruteador.use(RUTAS.SETS_PRODUCTOS.BASE, rutasEliminarSetsProductos); +//RF[44] Actualizar set de productos - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF44] +ruteador.use(RUTAS.SETS_PRODUCTOS.BASE, rutasActualizarSetsProductos); + module.exports = ruteador; diff --git a/Utilidades/Constantes/consultasSetsProductos.js b/Utilidades/Constantes/consultasSetsProductos.js index 741e9a16..d660922c 100644 --- a/Utilidades/Constantes/consultasSetsProductos.js +++ b/Utilidades/Constantes/consultasSetsProductos.js @@ -27,4 +27,7 @@ module.exports = { DELETE FROM set_producto WHERE idSetProducto = ?; `, + ACTUALIZAR: ` + UPDATE set_producto SET idCliente = ?, nombre = ?, nombreVisible = ?, descripcion = ?, activo = ? WHERE idSetProducto = ?; + `, }; diff --git a/Utilidades/Constantes/mensajesSetsProductos.js b/Utilidades/Constantes/mensajesSetsProductos.js index f887f58c..053dd73f 100644 --- a/Utilidades/Constantes/mensajesSetsProductos.js +++ b/Utilidades/Constantes/mensajesSetsProductos.js @@ -8,6 +8,10 @@ module.exports = { codigo: 200, mensaje: 'Set de productos eliminado correctamente.', }, + SET_PRODUCTOS_ACTUALIZADO: { + codigo: 200, + mensaje: 'Set de productos actualizado correctamente.', + }, // 204 - No Content SIN_RESULTADOS: { @@ -36,4 +40,8 @@ module.exports = { codigo: 500, mensaje: 'Ocurrió un error al eliminar el set de productos.', }, + ERROR_ACTUALIZAR_SET_PRODUCTOS: { + codigo: 500, + mensaje: 'Ocurrió un error al actualizar el set de productos', + }, }; diff --git a/Utilidades/Constantes/rutas.js b/Utilidades/Constantes/rutas.js index 92946212..daa02ac2 100644 --- a/Utilidades/Constantes/rutas.js +++ b/Utilidades/Constantes/rutas.js @@ -8,7 +8,7 @@ module.exports = { CERRAR_SESION: '/cerrar-sesion', USUARIO_AUTENTICADO: '/autenticar', ACTIVAR_2FA: '/activar-2fa', - VERIFICAR_2FA: '/verificar-2fa' + VERIFICAR_2FA: '/verificar-2fa', }, USUARIOS: { BASE: '/usuarios', @@ -52,6 +52,7 @@ module.exports = { CREAR: '/crear', SUBIR_IMAGEN: '/subir-imagen', ELIMINAR_SET_PRODUCTOS: '/eliminar', + ACTUALIZAR: '/actualizar', }, CLIENTES: { BASE: '/clientes', From f711cda1b2adf5258c7ff3ec32919b6c7d7dd489 Mon Sep 17 00:00:00 2001 From: PAOLA MARIA garrido Date: Fri, 30 May 2025 17:51:09 -0600 Subject: [PATCH 041/116] fix: cambiar a exportar por seleccion y agregar correciones de formato --- .../exportarEmpleados.controller.js | 53 ++++++++++++------- .../repositorioExportarEmpleado.js | 22 ++++---- .../exportarEmpleados.routes.js | 2 +- Utilidades/Constantes/consultasEmpleados.js | 35 ++++++------ package-lock.json | 10 ++++ package.json | 1 + 6 files changed, 75 insertions(+), 48 deletions(-) diff --git a/Empleados/Controladores/exportarEmpleados.controller.js b/Empleados/Controladores/exportarEmpleados.controller.js index 9b544e1f..8c7e61f3 100644 --- a/Empleados/Controladores/exportarEmpleados.controller.js +++ b/Empleados/Controladores/exportarEmpleados.controller.js @@ -1,22 +1,31 @@ const repositorio = require('@altertex/emp/repos/repositorioExportarEmpleado'); const { Parser } = require('json2csv'); +const { format } = require('date-fns'); const MENSAJES_EMPLEADOS = require('@altertex/util/const/mensajesEmpleados'); /** - * Controlador para exportar empleados y retornar CSV como string en JSON. - * + * Controlador para exportar empleados seleccionados de un cliente y retornar CSV como string en JSON. * - * * @async * @function exportarEmpleados * @param {Request} req * @param {Response} res * @returns {Response} JSON con mensaje + contenido CSV en texto plano - * @see [RF59 - Documentación de requisitos](https://codeandco-wiki.netlify.app/docs/next/proyectos/textiles/documentacion/requisitos/RF59) + * @see [RF59 - Exportar Empleados](https://codeandco-wiki.netlify.app/docs/next/proyectos/textiles/documentacion/requisitos/RF59) */ exports.exportarEmpleados = async (req, res) => { try { - const empleados = await repositorio.obtenerEmpleadosExportacion(); + const idCliente = parseInt(req.user.clienteSeleccionado); + const idsEmpleado = req.body.idsEmpleado; + + if (!Array.isArray(idsEmpleado) || idsEmpleado.length === 0) { + return res.status(MENSAJES_EMPLEADOS.PARAMETROS_INVALIDOS.codigo).json({ + mensaje: 'Debes seleccionar al menos un empleado para exportar.' + }); + } + + const idsSeleccionados = idsEmpleado.map(id => parseInt(id)); + const empleados = await repositorio.obtenerEmpleadosExportacion(idCliente, idsSeleccionados); if (!empleados || empleados.length === 0) { return res.status(MENSAJES_EMPLEADOS.EMPLEADOS_NO_ENCONTRADOS.codigo).json({ @@ -25,28 +34,34 @@ exports.exportarEmpleados = async (req, res) => { }); } + empleados.forEach(emp => { + emp.fechaNacimiento = format(new Date(emp.fechaNacimiento), 'dd/MM/yyyy'); + emp.antiguedad = format(new Date(emp.antiguedad), 'dd/MM/yyyy'); + }); + const campos = [ - { label: 'ID', value: 'idEmpleado' }, - { label: 'Nombre completo', value: 'nombreCompleto' }, - { label: 'Correo electrónico', value: 'correoElectronico' }, - { label: 'Número de teléfono', value: 'numeroTelefono' }, - { label: 'Dirección', value: 'direccion' }, - { label: 'Fecha de nacimiento', value: 'fechaNacimiento' }, - { label: 'Género', value: 'genero' }, - { label: 'Estatus', value: 'estatus' }, - { label: 'Número de emergencia', value: 'numeroEmergencia' }, - { label: 'Área de trabajo', value: 'areaTrabajo' }, - { label: 'Posición', value: 'posicion' }, - { label: 'Cantidad de puntos', value: 'cantidadPuntos' }, - { label: 'Antigüedad', value: 'antiguedad' } + { label: 'ID', value: 'idEmpleado' }, + { label: 'Nombre completo', value: 'nombreCompleto' }, + { label: 'Correo electrónico', value: 'correoElectronico' }, + { label: 'Número de teléfono', value: 'numeroTelefono' }, + { label: 'Dirección', value: 'direccion' }, + { label: 'Fecha de nacimiento', value: 'fechaNacimiento' }, + { label: 'Género', value: 'genero' }, + { label: 'Estatus', value: 'estatus' }, + { label: 'Número de emergencia', value: 'numeroEmergencia' }, + { label: 'Área de trabajo', value: 'areaTrabajo' }, + { label: 'Posición', value: 'posicion' }, + { label: 'Cantidad de puntos', value: 'cantidadPuntos' }, + { label: 'Antigüedad', value: 'antiguedad' } ]; const parser = new Parser({ fields: campos }); const csv = parser.parse(empleados); + const csvConBOM = '\uFEFF' + csv; return res.status(MENSAJES_EMPLEADOS.LISTA_EMPLEADOS_EXPORTADA.codigo).json({ mensaje: MENSAJES_EMPLEADOS.LISTA_EMPLEADOS_EXPORTADA.mensaje, - csv // string con contenido CSV para que el frontend lo descargue + csv: csvConBOM }); } catch (error) { console.error('Error al exportar empleados:', error); diff --git a/Empleados/Datos/Repositorios/repositorioExportarEmpleado.js b/Empleados/Datos/Repositorios/repositorioExportarEmpleado.js index 74aae58c..5e49be83 100644 --- a/Empleados/Datos/Repositorios/repositorioExportarEmpleado.js +++ b/Empleados/Datos/Repositorios/repositorioExportarEmpleado.js @@ -2,17 +2,17 @@ const correrQuery = require('@altertex/util/ser/correrQuery'); const CONSULTAS_EMPLEADOS = require('@altertex/util/const/consultasEmpleados'); /** - * Consulta la lista de empleados con todos los datos necesarios para exportar en CSV. - * - * @function - * @async - * @returns {Promise>} Arreglo de empleados con sus datos combinados (usuario + empleado). - * - * @throws {Error} Lanza un error si ocurre un fallo al ejecutar la consulta a la base de datos. + * Consulta la lista de empleados seleccionados de un cliente para exportar en CSV. * + * @param {number} idCliente + * @param {number[]} idsEmpleado + * @returns {Promise>} + * * @see [RF59 - Documentación de requisitos](https://codeandco-wiki.netlify.app/docs/next/proyectos/textiles/documentacion/requisitos/RF59) */ -exports.obtenerEmpleadosExportacion = () => { - const query = CONSULTAS_EMPLEADOS.OBTENER_DATOS_EXPORTACION; - return correrQuery(query); -}; + +exports.obtenerEmpleadosExportacion = (idCliente, idsEmpleado) => { + const placeholders = idsEmpleado.map(() => '?').join(', '); + const query = CONSULTAS_EMPLEADOS.OBTENER_DATOS_EXPORTACION.replace('__IDS__', placeholders); + return correrQuery(query, [idCliente, ...idsEmpleado]); +}; \ No newline at end of file diff --git a/Empleados/Rutas/RutasIndividuales/exportarEmpleados.routes.js b/Empleados/Rutas/RutasIndividuales/exportarEmpleados.routes.js index faa3b8a4..ae14f0dc 100644 --- a/Empleados/Rutas/RutasIndividuales/exportarEmpleados.routes.js +++ b/Empleados/Rutas/RutasIndividuales/exportarEmpleados.routes.js @@ -84,7 +84,7 @@ const limitePeticionesDiarias = require('@altertex/util/inter/limitePeticiones') * example: Error al exportar la lista de empleados. */ -ruteador.get( +ruteador.post( RUTAS.EMPLEADOS.EXPORTAR_EMPLEADOS, revisarApiKey(), autorizarToken, diff --git a/Utilidades/Constantes/consultasEmpleados.js b/Utilidades/Constantes/consultasEmpleados.js index d8733a9d..6648231f 100644 --- a/Utilidades/Constantes/consultasEmpleados.js +++ b/Utilidades/Constantes/consultasEmpleados.js @@ -25,21 +25,22 @@ module.exports = { cantidadPuntos = ?, antiguedad = ? WHERE idEmpleado = ?; `, OBTENER_DATOS_EXPORTACION: ` - SELECT - e.idEmpleado, - u.nombreCompleto, - u.correoElectronico, - u.numeroTelefono, - u.direccion, - u.fechaNacimiento, - u.genero, - u.estatus, - e.numeroEmergencia, - e.areaTrabajo, - e.posicion, - e.cantidadPuntos, - e.antiguedad - FROM empleado e - JOIN usuario u ON e.idUsuario = u.idUsuario; - `, + SELECT + e.idEmpleado, + u.nombreCompleto, + u.correoElectronico, + u.numeroTelefono, + u.direccion, + u.fechaNacimiento, + u.genero, + u.estatus, + e.numeroEmergencia, + e.areaTrabajo, + e.posicion, + e.cantidadPuntos, + e.antiguedad + FROM empleado e + JOIN usuario u ON e.idUsuario = u.idUsuario + WHERE e.idCliente = ? AND e.idEmpleado IN (__IDS__); + `, }; diff --git a/package-lock.json b/package-lock.json index 5d42a497..49b4a641 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "body-parser": "^1.20.3", "cookie-parser": "^1.4.7", "cors": "^2.8.5", + "date-fns": "^4.1.0", "dotenv": "^16.4.7", "express": "^4.21.2", "helmet": "^8.1.0", @@ -5741,6 +5742,15 @@ "node": ">= 14" } }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/dayjs": { "version": "1.11.13", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", diff --git a/package.json b/package.json index ce3f9c08..c09afccf 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "body-parser": "^1.20.3", "cookie-parser": "^1.4.7", "cors": "^2.8.5", + "date-fns": "^4.1.0", "dotenv": "^16.4.7", "express": "^4.21.2", "helmet": "^8.1.0", From 162ae4233f500d79daaa940d18bc518daa647230 Mon Sep 17 00:00:00 2001 From: max Date: Fri, 30 May 2025 18:31:22 -0600 Subject: [PATCH 042/116] feat(usuarios): add update user route and controller implementation --- .../actualizarEmpleado.routes.js | 1 - .../actualizarUsuario.controller.js | 35 +-- .../repositorioActualizarUsuario.js | 1 + .../actualizarUsuario.routes.js | 227 ++++++++++++++++++ Usuarios/Rutas/indexUsuarios.routes.js | 1 + Utilidades/Constantes/consultasUsuarios.js | 22 +- Utilidades/Constantes/rutas.js | 3 +- 7 files changed, 266 insertions(+), 24 deletions(-) diff --git a/Empleados/Rutas/RutasIndividuales/actualizarEmpleado.routes.js b/Empleados/Rutas/RutasIndividuales/actualizarEmpleado.routes.js index 544300ab..fabe5d4e 100644 --- a/Empleados/Rutas/RutasIndividuales/actualizarEmpleado.routes.js +++ b/Empleados/Rutas/RutasIndividuales/actualizarEmpleado.routes.js @@ -10,7 +10,6 @@ const revisarPermisos = require('@altertex/util/inter/verificarPermisos'); const validarYSanitizar = require('@altertex/util/inter/validarYSanitizar'); const limitePeticionesDiarias = require('@altertex/util/inter/limitePeticiones'); - //RF[19] Actualizar Empleado - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF19] /** diff --git a/Usuarios/Controladores/actualizarUsuario.controller.js b/Usuarios/Controladores/actualizarUsuario.controller.js index 8569655c..6e58b502 100644 --- a/Usuarios/Controladores/actualizarUsuario.controller.js +++ b/Usuarios/Controladores/actualizarUsuario.controller.js @@ -1,6 +1,7 @@ const MENSAJES = require('@altertex/util/const/mensajesUsuarios'); -const repositorio = require('@altertex/emp/repos/repositorioActualizarUsuario'); +const repositorio = require('@altertex/usu/repos/repositorioActualizarUsuario'); //RF[4] Actualizar Usuario - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF4] + /** * Controlador para actualizar la información de un usuario. * @@ -30,20 +31,20 @@ exports.actualizarUsuario = async (req, res) => { .status(MENSAJES.ERROR_ACTUALIZAR_USUARIO.codigo) .json({ mensaje: MENSAJES.ERROR_ACTUALIZAR_USUARIO.mensaje }); } -}; -if (!datos || datos.length === 0) { - return res - .status(MENSAJES.ERROR_ACTUALIZAR_USUARIO.codigo) - .json({ mensaje: MENSAJES.ERROR_ACTUALIZAR_USUARIO.mensaje }); -} -try { - await repositorio.actualizarUsuario(datos); - return res - .status(MENSAJES.USUARIO_ACTUALIZADO.codigo) - .json({ mensaje: MENSAJES.USUARIO_ACTUALIZADO.mensaje, datos }); -} catch { - return res - .status(MENSAJES.ERROR_ACTUALIZAR_USUARIO.codigo) - .json({ mensaje: MENSAJES.ERROR_ACTUALIZAR_USUARIO.mensaje }); -} + if (!datos || datos.length === 0) { + return res + .status(MENSAJES.ERROR_ACTUALIZAR_USUARIO.codigo) + .json({ mensaje: MENSAJES.ERROR_ACTUALIZAR_USUARIO.mensaje }); + } + try { + await repositorio.actualizarUsuario(datos); + return res + .status(MENSAJES.USUARIO_ACTUALIZADO.codigo) + .json({ mensaje: MENSAJES.USUARIO_ACTUALIZADO.mensaje, datos }); + } catch { + return res + .status(MENSAJES.ERROR_ACTUALIZAR_USUARIO.codigo) + .json({ mensaje: MENSAJES.ERROR_ACTUALIZAR_USUARIO.mensaje }); + } +}; diff --git a/Usuarios/Datos/Repositorios/repositorioActualizarUsuario.js b/Usuarios/Datos/Repositorios/repositorioActualizarUsuario.js index c4854ef8..c9b0d69a 100644 --- a/Usuarios/Datos/Repositorios/repositorioActualizarUsuario.js +++ b/Usuarios/Datos/Repositorios/repositorioActualizarUsuario.js @@ -3,6 +3,7 @@ const MENSAJES = require('@altertex/util/const/mensajesUsuarios'); const CONSULTAS_USUARIOS = require('@altertex/util/const/consultasUsuarios'); //RF[4] Actualizar Usuario - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF4 + /** * Repositorio para actualizar los datos de un usuario en la BD. * diff --git a/Usuarios/Rutas/RutasIndividuales/actualizarUsuario.routes.js b/Usuarios/Rutas/RutasIndividuales/actualizarUsuario.routes.js index e69de29b..d3239e98 100644 --- a/Usuarios/Rutas/RutasIndividuales/actualizarUsuario.routes.js +++ b/Usuarios/Rutas/RutasIndividuales/actualizarUsuario.routes.js @@ -0,0 +1,227 @@ +const express = require('express'); +const ruteador = express.Router(); +const PERMISOS = require('@altertex/util/const/permisos'); +const RUTAS = require('@altertex/util/const/rutas'); +const controlador = require('@altertex/usu/ctrl/actualizarUsuario.controller'); +const revisarApiKey = require('@altertex/util/inter/revisarApiKey'); +const autorizarToken = require('@altertex/util/inter/autorizarToken'); +const revisarPermisos = require('@altertex/util/inter/verificarPermisos'); +const validarYSanitizar = require('@altertex/util/inter/validarYSanitizar'); +const limitePeticionesDiarias = require('@altertex/util/inter/limitePeticiones'); + +//RF[4] Actualizar Usuario - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF4] + +/** + * @swagger + * /api/usuarios/actualizar: + * put: + * summary: Actualiza la información de un usuario. + * tags: + * - Usuarios + * security: + * - ApiKeyAuth: [] + * - BearerAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * oneOf: + * - type: object + * description: Información del usuario a actualizar directamente + * required: + * - idUsuario + * - nombreCompleto + * - correoElectronico + * - contrasenia + * - numeroTelefono + * - direccion + * - fechaNacimiento + * - genero + * - estatus + * - idRol + * properties: + * idUsuario: + * type: integer + * example: 30 + * nombreCompleto: + * type: string + * example: "Luis Hernández" + * correoElectronico: + * type: string + * example: "lhernandez@gmail.com" + * contrasenia: + * type: string + * example: "NuevaContraseniaSegura123" + * numeroTelefono: + * type: string + * example: "5551234567" + * direccion: + * type: string + * example: "Av. Revolución 123, CDMX" + * fechaNacimiento: + * type: string + * format: date + * example: "1995-06-15" + * genero: + * type: string + * enum: [Masculino, Femenino, Otro] + * example: "Masculino" + * estatus: + * type: boolean + * example: true + * idRol: + * type: integer + * example: 2 + * - type: object + * properties: + * usuarios: + * oneOf: + * - type: object + * required: + * - idUsuario + * - nombreCompleto + * - correoElectronico + * - contrasenia + * - numeroTelefono + * - direccion + * - fechaNacimiento + * - genero + * - estatus + * - idRol + * properties: + * idUsuario: + * type: integer + * example: 30 + * nombreCompleto: + * type: string + * example: "Luis Hernández" + * correoElectronico: + * type: string + * example: "lhernandez@gmail.com" + * contrasenia: + * type: string + * example: "NuevaContraseniaSegura123" + * numeroTelefono: + * type: string + * example: "5551234567" + * direccion: + * type: string + * example: "Av. Revolución 123, CDMX" + * fechaNacimiento: + * type: string + * format: date + * example: "1995-06-15" + * genero: + * type: string + * example: "Masculino" + * estatus: + * type: boolean + * example: true + * idRol: + * type: integer + * example: 2 + * - type: array + * items: + * type: object + * required: + * - idUsuario + * - nombreCompleto + * - correoElectronico + * - contrasenia + * - numeroTelefono + * - direccion + * - fechaNacimiento + * - genero + * - estatus + * - idRol + * properties: + * idUsuario: + * type: integer + * example: 30 + * nombreCompleto: + * type: string + * example: "Luis Hernández" + * correoElectronico: + * type: string + * example: "lhernandez@gmail.com" + * contrasenia: + * type: string + * example: "NuevaContraseniaSegura123" + * numeroTelefono: + * type: string + * example: "5551234567" + * direccion: + * type: string + * example: "Av. Revolución 123, CDMX" + * fechaNacimiento: + * type: string + * format: date + * example: "1995-06-15" + * genero: + * type: string + * example: "Masculino" + * estatus: + * type: boolean + * example: true + * idRol: + * type: integer + * example: 2 + * responses: + * 200: + * description: Información del usuario actualizada correctamente. + * content: + * application/json: + * schema: + * type: object + * properties: + * mensaje: + * type: string + * example: "Usuario actualizado con éxito." + * datos: + * type: array + * items: + * type: object + * 400: + * description: Error en los datos enviados o en la actualización. + * content: + * application/json: + * schema: + * type: object + * properties: + * mensaje: + * type: string + * example: "Error al actualizar el usuario." + * x-codeSamples: + * - lang: JavaScript + * label: cURL + * source: | + * curl -X PUT "https://tu-api.com/api/usuarios/actualizar" \ + * -H "x-api-key: TU_API_KEY" \ + * -H "Authorization: Bearer TU_TOKEN" \ + * -H "Content-Type: application/json" \ + * -d '{ + * "idUsuario": 30, + * "nombreCompleto": "Luis Hernández", + * "correoElectronico": "lhernandez@gmail.com", + * "contrasenia": "NuevaContraseniaSegura123", + * "numeroTelefono": "5551234567", + * "direccion": "Av. Revolución 123, CDMX", + * "fechaNacimiento": "1995-06-15", + * "genero": "Masculino", + * "estatus": true, + * "idRol": 2 + * }' + */ + +ruteador.put( + RUTAS.USUARIOS.ACTUALIZAR, + revisarApiKey(), + validarYSanitizar, + autorizarToken, + limitePeticionesDiarias, + revisarPermisos(PERMISOS.ACTUALIZAR_USUARIO), + controlador.actualizarUsuario +); + +module.exports = ruteador; diff --git a/Usuarios/Rutas/indexUsuarios.routes.js b/Usuarios/Rutas/indexUsuarios.routes.js index 9926a904..f6fa16d0 100644 --- a/Usuarios/Rutas/indexUsuarios.routes.js +++ b/Usuarios/Rutas/indexUsuarios.routes.js @@ -12,5 +12,6 @@ ruteador.use(RUTAS.USUARIOS.BASE, rutasConsultarListaUsuarios); ruteador.use(RUTAS.USUARIOS.BASE, rutasCrearUsuario); ruteador.use(RUTAS.USUARIOS.BASE, rutasLeerUsuario); ruteador.use(RUTAS.USUARIOS.BASE, rutasEliminarUsuario); +ruteador.use(RUTAS.USUARIOS.BASE, rutasActualizarUsuario); module.exports = ruteador; diff --git a/Utilidades/Constantes/consultasUsuarios.js b/Utilidades/Constantes/consultasUsuarios.js index 7768c4a6..ff9fc335 100644 --- a/Utilidades/Constantes/consultasUsuarios.js +++ b/Utilidades/Constantes/consultasUsuarios.js @@ -94,11 +94,23 @@ module.exports = { WHERE idUsuario = (?); `, - ACTUALIZAR: ` - UPDATE usuario SET - nombreCompleto = ?, correoElectronico = ?, estatus = ? - , - cantidadPuntos = ?, antiguedad = ? WHERE idUsuario = ?; + ACTUALIZAR_DATOS_USUARIO: ` +UPDATE usuario SET + nombreCompleto = ?, + correoElectronico = ?, + contrasenia = ?, + numeroTelefono = ?, + direccion = ?, + fechaNacimiento = ?, + genero = ?, + estatus = ? +WHERE idUsuario = ?; + `, + + ACTUALIZAR_ROL_USUARIO: ` +UPDATE usuario_rol SET +idRol = ? +WHERE idUsuario = ?; `, VALIDAR_CORREO: ` diff --git a/Utilidades/Constantes/rutas.js b/Utilidades/Constantes/rutas.js index 92946212..31f8128c 100644 --- a/Utilidades/Constantes/rutas.js +++ b/Utilidades/Constantes/rutas.js @@ -8,7 +8,7 @@ module.exports = { CERRAR_SESION: '/cerrar-sesion', USUARIO_AUTENTICADO: '/autenticar', ACTIVAR_2FA: '/activar-2fa', - VERIFICAR_2FA: '/verificar-2fa' + VERIFICAR_2FA: '/verificar-2fa', }, USUARIOS: { BASE: '/usuarios', @@ -16,6 +16,7 @@ module.exports = { CREAR: '/crear', ELIMINAR_USUARIOS: '/eliminar-usuarios', LEER: '/consultar-usuario', + ACTUALIZAR: '/actualizar-usuario', }, CATEGORIAS: { BASE: '/categorias', From e0951b9678a708c44f726a6fd58f2fdd3072b7d8 Mon Sep 17 00:00:00 2001 From: NicoH00d Date: Fri, 30 May 2025 18:59:28 -0600 Subject: [PATCH 043/116] feat: agregar validaciones para importar producto --- .../importarProductos.controller.js | 11 +- .../Validaciones/validarOpcionesImportar.js | 86 +++++++++++ .../Validaciones/validarProductoImportado.js | 141 ++++++++++++++++++ 3 files changed, 232 insertions(+), 6 deletions(-) create mode 100644 Utilidades/Intermediarios/Validaciones/validarOpcionesImportar.js create mode 100644 Utilidades/Intermediarios/Validaciones/validarProductoImportado.js diff --git a/Productos/Controladores/importarProductos.controller.js b/Productos/Controladores/importarProductos.controller.js index 57e85aa2..238e0843 100644 --- a/Productos/Controladores/importarProductos.controller.js +++ b/Productos/Controladores/importarProductos.controller.js @@ -1,10 +1,10 @@ -const validarProducto = require('@altertex/util/vali/validarProducto'); const validarVariante = require('@altertex/util/vali/validarVariante'); -const validarOpciones = require('@altertex/util/vali/validarOpciones'); +const validarOpcionesImportar = require('@altertex/util/vali/validarOpcionesImportar'); const repositorioCrearProducto = require('@altertex/pro/repos/repositorioCrearProducto'); const repositorioCrearVariante = require('@altertex/pro/repos/repositorioCrearVariante'); const repositorioCrearOpcion = require('@altertex/pro/repos/repositorioCrearOpcion'); const db = require('@altertex/util/bd/db'); +const validarProductoImportado = require('@altertex/util/vali/validarProductoImportado'); /** @@ -35,7 +35,6 @@ const db = require('@altertex/util/bd/db'); exports.importarProductos = async (req, res) => { const idCliente = parseInt(req.user.clienteSeleccionado); const productos = req.body; // Espera array de { producto, variantes } - if (!Array.isArray(productos) || productos.length === 0) { return res.status(400).json({ mensaje: 'No se recibieron productos válidos.' }); } @@ -51,7 +50,7 @@ exports.importarProductos = async (req, res) => { const { producto, variantes } = productos[im]; const fila = im + 1; - const errorProducto = validarProducto(producto); + const errorProducto = validarProductoImportado(producto); if (errorProducto) { errores.push({ fila, error: errorProducto.error }); continue; @@ -80,8 +79,8 @@ exports.importarProductos = async (req, res) => { errores.push({ fila, error: 'Error al crear variante.' }); continue; } - - const errorOpciones = validarOpciones(variante.opciones); + console.log(variante.opciones) + const errorOpciones = validarOpcionesImportar(variante.opciones); if (errorOpciones) { errores.push({ fila, error: errorOpciones.error }); continue; diff --git a/Utilidades/Intermediarios/Validaciones/validarOpcionesImportar.js b/Utilidades/Intermediarios/Validaciones/validarOpcionesImportar.js new file mode 100644 index 00000000..2d3970e1 --- /dev/null +++ b/Utilidades/Intermediarios/Validaciones/validarOpcionesImportar.js @@ -0,0 +1,86 @@ +//RF26 Crea Producto - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF26 +/** + * Valida un conjunto de opciones para un producto. + * + * Esta función valida cada opción dentro del arreglo de opciones pasado como argumento. + * Se asegura de que los valores de las propiedades cumplan con los tipos y rangos especificados, + * y retorna un objeto de error si algún valor es inválido. + * + * @param {Array} opciones - Un arreglo de objetos que representan las opciones de un producto. + * @param {number} opciones[].cantidad - La cantidad de la opción, que debe ser un número entero positivo o cero. + * @param {string} opciones[].valorOpcion - El valor de la opción, que debe ser una cadena de texto de máximo 100 caracteres. + * @param {string} [opciones[].SKUautomatico] - El SKU automático de la opción, que debe ser una cadena de texto de máximo 50 caracteres, si se proporciona. + * @param {string} [opciones[].SKUcomercial] - El SKU comercial de la opción, que debe ser una cadena de texto de máximo 50 caracteres, si se proporciona. + * @param {number} opciones[].costoAdicional - El costo adicional de la opción, que debe ser un número positivo o cero. + * @param {number} opciones[].descuento - El descuento de la opción, que debe ser un número entre 0 y 100. + * @param {number} opciones[].estado - El estado de la opción, que debe ser 1 (activo) o 0 (inactivo). + * + * @returns {object|null} Un objeto con una propiedad `error` si hay un error de validación, o `null` si todas las opciones son válidas. + * @example + * const opciones = [ + * { + * cantidad: 5, + * valorOpcion: 'Tamaño M', + * SKUautomatico: 'SKU123', + * SKUcomercial: 'S123', + * costoAdicional: 15.50, + * descuento: 10, + * estado: 1 + * } + * ]; + * + * const resultado = validarOpciones(opciones); + * console.log(resultado); // null si todo está bien, o un objeto de error si algo es inválido + */ +module.exports = (opciones) => { + for (const opcion of opciones) { + // prettier-ignore + if ( + typeof opcion.cantidad !== 'number' + || opcion.cantidad < 0 + || !Number.isInteger(opcion.cantidad) + ) { + return { error: 'cantidad de la opción debe ser un número entero positivo o cero.' }; + } + + // prettier-ignore + if ( + !opcion.valorOpcion + || typeof opcion.valorOpcion !== 'string' + || opcion.valorOpcion.length > 100 + ) { + return { + error: 'valorOpcion es requerido y debe ser una cadena de texto de máximo 100 caracteres.', + }; + } + + // prettier-ignore + if (opcion.SKUautomatico == null + || typeof opcion.SKUautomatico !== 'string' + || opcion.SKUautomatico.length > 50 + ) { + return { error: 'SKUautomatico es requerido y debe ser una cadena de texto de máximo 50 caracteres.' }; + } + // prettier-ignore + if (opcion.SKUcomercial == null + || typeof opcion.SKUcomercial !== 'string' + || opcion.SKUcomercial.length > 50 + ) { + return { error: 'SKUcomercial debe ser una cadena de texto de máximo 50 caracteres.' }; + } + + if (typeof opcion.costoAdicional !== 'number' || opcion.costoAdicional < 0) { + return { error: 'costoAdicional debe ser un número positivo o cero.' }; + } + + if (typeof opcion.descuento !== 'number' || opcion.descuento < 0 || opcion.descuento > 100) { + return { error: 'descuento debe ser un número entre 0 y 100.' }; + } + + if (typeof opcion.estado !== 'number' || (opcion.estado !== 0 && opcion.estado !== 1)) { + return { error: 'estado debe ser 1 (activo) o 0 (inactivo).' }; + } + } + + return null; +}; diff --git a/Utilidades/Intermediarios/Validaciones/validarProductoImportado.js b/Utilidades/Intermediarios/Validaciones/validarProductoImportado.js new file mode 100644 index 00000000..fdeee7dd --- /dev/null +++ b/Utilidades/Intermediarios/Validaciones/validarProductoImportado.js @@ -0,0 +1,141 @@ +//RF26 Crea Producto - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF26 +/** + * Valida los campos de un producto. + * + * Esta función valida los diferentes campos de un objeto `producto` asegurándose de que cumplan con los tipos y restricciones especificados. Si algún campo no cumple con las condiciones, se devuelve un objeto de error con un mensaje específico. Si todos los campos son válidos, se retorna `null`. + * + * @param {object} producto - El objeto que representa un producto a validar. + * @param {number|null} producto.idProveedor - El ID del proveedor, debe ser un número entero positivo o `null`. + * @param {string} producto.nombreComun - El nombre común del producto, debe ser una cadena de texto de máximo 100 caracteres. + * @param {string|null} producto.nombreComercial - El nombre comercial del producto, debe ser una cadena de texto de máximo 150 caracteres o `null`. + * @param {string|null} producto.descripcion - Descripción del producto, debe ser una cadena de texto de máximo 1000 caracteres o `null`. + * @param {string|null} producto.marca - La marca del producto, debe ser una cadena de texto de máximo 100 caracteres o `null`. + * @param {string|null} producto.modelo - El modelo del producto, debe ser una cadena de texto de máximo 100 caracteres o `null`. + * @param {string|null} producto.tipoProducto - El tipo de producto, debe ser una cadena de texto de máximo 50 caracteres o `null`. + * @param {number|null} producto.precioPuntos - El precio en puntos, debe ser un número entero positivo o `null`. + * @param {number|null} producto.precioCliente - El precio para el cliente, debe ser un número mayor o igual a cero o `null`. + * @param {number|null} producto.precioVenta - El precio de venta, debe ser un número mayor o igual a cero o `null`. + * @param {number|null} producto.costo - El costo del producto, debe ser un número mayor o igual a cero o `null`. + * @param {number|null} producto.impuesto - El impuesto del producto, debe ser un número mayor o igual a cero o `null`. + * @param {number|null} producto.descuento - El descuento del producto, debe ser un número mayor o igual a cero o `null`. + * @param {number} producto.estado - El estado del producto, debe ser 0 (inactivo) o 1 (activo). + * @param {number} producto.envio - Indica si el envío está disponible, debe ser 0 (no disponible) o 1 (disponible). + * + * @returns {object|null} Un objeto con una propiedad `error` si hay un error de validación, o `null` si todos los campos son válidos. + * @example + * const producto = { + * idProveedor: 1, + * nombreComun: 'Producto X', + * nombreComercial: 'Producto X Comercial', + * descripcion: 'Descripción del producto', + * marca: 'Marca X', + * modelo: 'Modelo 123', + * tipoProducto: 'Tipo A', + * precioPuntos: 100, + * precioCliente: 200.50, + * precioVenta: 250, + * costo: 150, + * impuesto: 25, + * descuento: 10, + * estado: 1, + * envio: 1, + * }; + * + * const resultado = validarProducto(producto); + * console.log(resultado); // null si todo está bien, o un objeto de error si algo es inválido + */ +// prettier-ignore +module.exports = (producto) => { + if ( + producto.idProveedor !== null + && (typeof producto.idProveedor !== 'number' + || producto.idProveedor <= 0 + || !Number.isInteger(producto.idProveedor)) + ) { + return { error: 'idProveedor debe ser un número entero positivo o NULL.' }; + } + + if ( + !producto.nombreComun + || typeof producto.nombreComun !== 'string' + || producto.nombreComun.length > 100 + ) { + return { + error: 'nombreComun es requerido, debe ser una cadena de texto y no exceder 100 caracteres.', + }; + } + + if ( + producto.nombreComercial !== null + && (typeof producto.nombreComercial !== 'string' || producto.nombreComercial.length > 150) + ) { + return { + error: 'nombreComercial debe ser una cadena de texto o NULL y no exceder 150 caracteres.', + }; + } + + if ( + producto.descripcion !== null + && (typeof producto.descripcion !== 'string' || producto.descripcion.length > 1000) + ) { + return { + error: 'descripcion debe ser una cadena de texto o NULL y no exceder 1000 caracteres.', + }; + } + + if ( + producto.marca !== null + && (typeof producto.marca !== 'string' || producto.marca.length > 100) + ) { + return { error: 'marca debe ser una cadena de texto o NULL y no exceder 100 caracteres.' }; + } + + if ( + producto.modelo !== null + && (typeof producto.modelo !== 'string' || producto.modelo.length > 100) + ) { + return { error: 'modelo debe ser una cadena de texto o NULL y no exceder 100 caracteres.' }; + } + + if ( + producto.tipoProducto !== null + && (typeof producto.tipoProducto !== 'string' || producto.tipoProducto.length > 50) + ) { + return { + error: 'tipoProducto debe ser una cadena de texto o NULL y no exceder 50 caracteres.', + }; + } + + const camposNumericos = [ + 'precioPuntos', + 'precioCliente', + 'precioVenta', + 'costo', + 'impuesto', + 'descuento', + ]; + + for (const campo of camposNumericos) { + const valor = producto[campo] !== null ? Number(producto[campo]) : null; + + if ( + valor !== null + && (Number.isNaN(valor) + || (campo === 'precioPuntos' && !Number.isInteger(valor)) + || (campo !== 'precioPuntos' && valor < 0)) + ) { + return { error: `${campo} debe ser un número válido y mayor o igual a cero.` }; + } +} + + + if (producto.estado !== undefined && producto.estado !== 0 && producto.estado !== 1) { + return { error: 'estado debe ser 0 (inactivo) o 1 (activo).' }; + } + + if (producto.envio !== undefined && producto.envio !== 0 && producto.envio !== 1) { + return { error: 'envio debe ser 0 (no disponible) o 1 (disponible).' }; + } + + return null; +}; From 4be124ad487f5b2220b56f8fa30b6e0244d3088e Mon Sep 17 00:00:00 2001 From: angieriosc Date: Sun, 1 Jun 2025 16:55:45 -0600 Subject: [PATCH 044/116] =?UTF-8?q?Fix:=20Correci=C3=B3n=20rutas=20actuali?= =?UTF-8?q?zar=20empleado=20y=20grupo=20de=20empleados?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repositorioLeerGrupoDeEmpleados.js | 9 +++++++ .../actualizarEmpleado.routes.js | 1 - .../actualizarGrupoEmpleados.routes.js | 2 +- Empleados/Rutas/indexEmpleados.routes.js | 6 ++--- .../Constantes/consultasGrupoEmpleados.js | 26 ++++++++++++++++++- Utilidades/Constantes/rutas.js | 3 ++- 6 files changed, 40 insertions(+), 7 deletions(-) diff --git a/Empleados/Datos/Repositorios/repositorioLeerGrupoDeEmpleados.js b/Empleados/Datos/Repositorios/repositorioLeerGrupoDeEmpleados.js index 4a4fa87d..c989946c 100644 --- a/Empleados/Datos/Repositorios/repositorioLeerGrupoDeEmpleados.js +++ b/Empleados/Datos/Repositorios/repositorioLeerGrupoDeEmpleados.js @@ -18,6 +18,13 @@ exports.obtenerGrupoEmpleadosPorId = async (idGrupo) => { const resultado = await correrQuery(query, [idGrupo]); if (resultado.length === 0) return null; + const setProductosOriginal = resultado[0].setProductosActualizar; + const setProductosFiltrados = Object.values( + setProductosOriginal.reduce((acc, obj) => { + acc[obj.id] = obj; // sobrescribe si ya existe ese id + return acc; + }, {}) + ); const grupoEmpleados = { idGrupo: resultado[0].idGrupo, @@ -31,6 +38,8 @@ exports.obtenerGrupoEmpleadosPorId = async (idGrupo) => { idsEmpleados: resultado[0].idsEmpleados ? resultado[0].idsEmpleados.split(',').map(Number) : [], + empleadosActualizar: resultado[0].empleadosActualizar, + setProductosActualizar: setProductosFiltrados, }; return grupoEmpleados; diff --git a/Empleados/Rutas/RutasIndividuales/actualizarEmpleado.routes.js b/Empleados/Rutas/RutasIndividuales/actualizarEmpleado.routes.js index 544300ab..fabe5d4e 100644 --- a/Empleados/Rutas/RutasIndividuales/actualizarEmpleado.routes.js +++ b/Empleados/Rutas/RutasIndividuales/actualizarEmpleado.routes.js @@ -10,7 +10,6 @@ const revisarPermisos = require('@altertex/util/inter/verificarPermisos'); const validarYSanitizar = require('@altertex/util/inter/validarYSanitizar'); const limitePeticionesDiarias = require('@altertex/util/inter/limitePeticiones'); - //RF[19] Actualizar Empleado - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF19] /** diff --git a/Empleados/Rutas/RutasIndividuales/actualizarGrupoEmpleados.routes.js b/Empleados/Rutas/RutasIndividuales/actualizarGrupoEmpleados.routes.js index 4a1dd3c1..d4c530bd 100644 --- a/Empleados/Rutas/RutasIndividuales/actualizarGrupoEmpleados.routes.js +++ b/Empleados/Rutas/RutasIndividuales/actualizarGrupoEmpleados.routes.js @@ -11,7 +11,7 @@ const controlador = require('@altertex/emp/ctrl/actualizarGrupoEmpleado.controll /** * @swagger - * /api/empleados/actualizar-grupos: + * /api/empleados/actualizar-grupo: * put: * summary: Actualiza un grupo de empleados * description: Actualiza el nombre, la descripción, los empleados y los sets de productos asociados a un grupo de empleados existente. diff --git a/Empleados/Rutas/indexEmpleados.routes.js b/Empleados/Rutas/indexEmpleados.routes.js index a1f7329d..8c6df835 100644 --- a/Empleados/Rutas/indexEmpleados.routes.js +++ b/Empleados/Rutas/indexEmpleados.routes.js @@ -7,8 +7,8 @@ const rutasEliminarEmpleado = require('@altertex/emp/rutasInd/eliminarEmpleado.r const rutasImportarEmpleados = require('@altertex/emp/rutasInd/importarEmpleados.routes'); const rutasLeerGrupoEmpleados = require('@altertex/emp/rutasInd/leerGrupoEmpleados.routes'); const rutasCrearGrupo = require('@altertex/emp/rutasInd/crearGrupoEmpleados.routes'); - const rutasActualizarEmpleado = require('@altertex/emp/rutasInd/actualizarEmpleado.routes'); +const rutasActualizarGrupo = require('@altertex/emp/rutasInd/actualizarGrupoEmpleados.routes'); const RUTAS = require('@altertex/util/const/rutas'); @@ -28,7 +28,7 @@ ruteador.use(RUTAS.EMPLEADOS.BASE, rutasLeerGrupoEmpleados); ruteador.use(RUTAS.EMPLEADOS.BASE, rutasCrearGrupo); //RF19 - Actualizar Empleado - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF19 ruteador.use(RUTAS.EMPLEADOS.BASE, rutasActualizarEmpleado); -// RF[24] Actualiza grupo empleado - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF24] -ruteador.use(RUTAS.EMPLEADOS.BASE, rutasActualizarEmpleado); +//RF24 - Actualizar Grupo de Empleados - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF24 +ruteador.use(RUTAS.EMPLEADOS.BASE, rutasActualizarGrupo); module.exports = ruteador; diff --git a/Utilidades/Constantes/consultasGrupoEmpleados.js b/Utilidades/Constantes/consultasGrupoEmpleados.js index 50c1b91b..2a8de6e4 100644 --- a/Utilidades/Constantes/consultasGrupoEmpleados.js +++ b/Utilidades/Constantes/consultasGrupoEmpleados.js @@ -24,14 +24,38 @@ module.exports = { ge.idGrupo, ge.nombre AS nombre, ge.descripcion AS descripcion, + IFNULL(GROUP_CONCAT(DISTINCT sp.nombre SEPARATOR ', '), 'Sin sets de productos asociados') AS setsProductos, IFNULL(GROUP_CONCAT(DISTINCT sp.idSetProducto SEPARATOR ','), '') AS idsSetProductos, + IFNULL(GROUP_CONCAT(DISTINCT CONCAT( u.nombreCompleto, ' | ', u.correoElectronico, ' | ', e.areaTrabajo ) SEPARATOR ' || '), 'Sin empleados asociados') AS infoEmpleados, - IFNULL(GROUP_CONCAT(DISTINCT e.idEmpleado SEPARATOR ','), '') AS idsEmpleados + + IFNULL(GROUP_CONCAT(DISTINCT e.idEmpleado SEPARATOR ','), '') AS idsEmpleados, + IFNULL( + JSON_ARRAYAGG( + JSON_OBJECT( + 'id', e.idEmpleado, + 'correo', u.correoElectronico, + 'nombre', u.nombreCompleto, + 'area', e.areaTrabajo + ) + ), + JSON_ARRAY() + ) AS empleadosActualizar, + IFNULL( + JSON_ARRAYAGG( + JSON_OBJECT( + 'id', sp.idSetProducto, + 'nombreProducto', sp.nombre, + 'activo', sp.activo + ) + ), + JSON_ARRAY() + ) AS setProductosActualizar FROM grupo_empleado ge LEFT JOIN empleado_grupo eg ON ge.idGrupo = eg.idGrupo LEFT JOIN empleado e ON eg.idEmpleado = e.idEmpleado diff --git a/Utilidades/Constantes/rutas.js b/Utilidades/Constantes/rutas.js index 766396f8..d5eea36e 100644 --- a/Utilidades/Constantes/rutas.js +++ b/Utilidades/Constantes/rutas.js @@ -71,7 +71,8 @@ module.exports = { IMPORTAR_EMPLEADOS: '/importar-empleados', LEER_GRUPO: '/leer-grupo', CREAR_GRUPO: '/crear-grupo', - ACTUALIZAR: '/actualizar-grupo', + ACTUALIZAR: '/actualizar', + ACTUALIZAR_GRUPO_EMPLEADO: '/actualizar-grupo', }, CUOTAS: { BASE: '/cuotas', From b8d1350a880ad944f2f95d0771d37b833d9f11fb Mon Sep 17 00:00:00 2001 From: angieriosc Date: Sun, 1 Jun 2025 18:37:56 -0600 Subject: [PATCH 045/116] Fix: productos actualizados quitar null --- .../repositorioLeerGrupoDeEmpleados.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/Empleados/Datos/Repositorios/repositorioLeerGrupoDeEmpleados.js b/Empleados/Datos/Repositorios/repositorioLeerGrupoDeEmpleados.js index c989946c..54831892 100644 --- a/Empleados/Datos/Repositorios/repositorioLeerGrupoDeEmpleados.js +++ b/Empleados/Datos/Repositorios/repositorioLeerGrupoDeEmpleados.js @@ -18,9 +18,15 @@ exports.obtenerGrupoEmpleadosPorId = async (idGrupo) => { const resultado = await correrQuery(query, [idGrupo]); if (resultado.length === 0) return null; - const setProductosOriginal = resultado[0].setProductosActualizar; + + // ✅ Manejo seguro de setProductosActualizar cuando es null o contiene objetos con valores null + const setProductosOriginal = resultado[0].setProductosActualizar || []; + + // Filtrar objetos que tienen id null (cuando no hay datos reales) + const setProductosValidos = setProductosOriginal.filter((obj) => obj.id !== null); + const setProductosFiltrados = Object.values( - setProductosOriginal.reduce((acc, obj) => { + setProductosValidos.reduce((acc, obj) => { acc[obj.id] = obj; // sobrescribe si ya existe ese id return acc; }, {}) @@ -38,8 +44,10 @@ exports.obtenerGrupoEmpleadosPorId = async (idGrupo) => { idsEmpleados: resultado[0].idsEmpleados ? resultado[0].idsEmpleados.split(',').map(Number) : [], - empleadosActualizar: resultado[0].empleadosActualizar, - setProductosActualizar: setProductosFiltrados, + empleadosActualizar: (resultado[0].empleadosActualizar || []).filter( + (obj) => obj.id !== null + ), + setProductosActualizar: setProductosFiltrados, // Ya no necesita validación adicional }; return grupoEmpleados; From 798d49f300774dce850eb7166268aaaf468ca77e Mon Sep 17 00:00:00 2001 From: NicoH00d Date: Sun, 1 Jun 2025 23:20:33 -0600 Subject: [PATCH 046/116] =?UTF-8?q?fix:=20canelar=20importaci=C3=B3n=20si?= =?UTF-8?q?=20hay=20errores?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../importarProductos.controller.js | 38 +++++++----- .../Validaciones/validarOpcionesImportar.js | 3 +- .../Validaciones/validarProductoImportado.js | 62 ++++++++++++++----- 3 files changed, 71 insertions(+), 32 deletions(-) diff --git a/Productos/Controladores/importarProductos.controller.js b/Productos/Controladores/importarProductos.controller.js index 238e0843..a0594a9b 100644 --- a/Productos/Controladores/importarProductos.controller.js +++ b/Productos/Controladores/importarProductos.controller.js @@ -35,6 +35,7 @@ const validarProductoImportado = require('@altertex/util/vali/validarProductoImp exports.importarProductos = async (req, res) => { const idCliente = parseInt(req.user.clienteSeleccionado); const productos = req.body; // Espera array de { producto, variantes } + if (!Array.isArray(productos) || productos.length === 0) { return res.status(400).json({ mensaje: 'No se recibieron productos válidos.' }); } @@ -46,7 +47,8 @@ exports.importarProductos = async (req, res) => { conexion = await db.getConnection(); await conexion.beginTransaction(); - for (let im = 0; im < productos.length; im = im + 1) { + // Validación previa de todos los productos + for (let im = 0; im < productos.length; im += 1) { const { producto, variantes } = productos[im]; const fila = im + 1; @@ -61,12 +63,6 @@ exports.importarProductos = async (req, res) => { continue; } - const idProducto = await repositorioCrearProducto.crearProducto(idCliente, producto); - if (!idProducto) { - errores.push({ fila, error: 'Error al crear producto.' }); - continue; - } - for (const variante of variantes) { const errorVariante = validarVariante(variante); if (errorVariante) { @@ -74,18 +70,29 @@ exports.importarProductos = async (req, res) => { continue; } - const idVariante = await repositorioCrearVariante.crearVariante(idProducto, variante); - if (!idVariante) { - errores.push({ fila, error: 'Error al crear variante.' }); - continue; - } - console.log(variante.opciones) const errorOpciones = validarOpcionesImportar(variante.opciones); if (errorOpciones) { errores.push({ fila, error: errorOpciones.error }); continue; } + } + } + + if (errores.length > 0) { + await conexion.rollback(); + return res.status(200).json({ + mensaje: 'Se encontraron errores en el archivo.', + errores, + }); + } + // Si no hubo errores, insertar todos los productos + for (let im = 0; im < productos.length; im += 1) { + const { producto, variantes } = productos[im]; + const idProducto = await repositorioCrearProducto.crearProducto(idCliente, producto); + + for (const variante of variantes) { + const idVariante = await repositorioCrearVariante.crearVariante(idProducto, variante); await repositorioCrearOpcion.crearOpcion(idVariante, variante.opciones); } } @@ -93,9 +100,10 @@ exports.importarProductos = async (req, res) => { await conexion.commit(); return res.status(200).json({ - mensaje: 'Importación completada.', - errores: errores.length ? errores : null, + mensaje: 'Importación completada exitosamente.', + errores: null, }); + } catch (err) { if (conexion) await conexion.rollback(); return res.status(500).json({ diff --git a/Utilidades/Intermediarios/Validaciones/validarOpcionesImportar.js b/Utilidades/Intermediarios/Validaciones/validarOpcionesImportar.js index 2d3970e1..5163c279 100644 --- a/Utilidades/Intermediarios/Validaciones/validarOpcionesImportar.js +++ b/Utilidades/Intermediarios/Validaciones/validarOpcionesImportar.js @@ -55,7 +55,8 @@ module.exports = (opciones) => { } // prettier-ignore - if (opcion.SKUautomatico == null + if (!opcion.SKUautomatico + || opcion.SKUautomatico == null || typeof opcion.SKUautomatico !== 'string' || opcion.SKUautomatico.length > 50 ) { diff --git a/Utilidades/Intermediarios/Validaciones/validarProductoImportado.js b/Utilidades/Intermediarios/Validaciones/validarProductoImportado.js index fdeee7dd..eaf26917 100644 --- a/Utilidades/Intermediarios/Validaciones/validarProductoImportado.js +++ b/Utilidades/Intermediarios/Validaciones/validarProductoImportado.js @@ -106,28 +106,58 @@ module.exports = (producto) => { }; } - const camposNumericos = [ - 'precioPuntos', - 'precioCliente', - 'precioVenta', - 'costo', - 'impuesto', - 'descuento', - ]; + if ( + !producto.costo + && (typeof producto.costo !== 'number' || producto.costo < 0 || Number.isNaN(producto.costo)) + ) { + return { + error: 'costo debe ser un número mayor o igual a cero', + }; + } + if ( + !producto.precioVenta + && (typeof producto.precioVenta !== 'number' || producto.precioVenta < 0 || Number.isNaN(producto.precioVenta)) + ) { + return { + error: 'precioVenta debe ser un número mayor o igual a cero', + }; + } + + if ( + !producto.precioCliente + && (typeof producto.precioCliente !== 'number' || producto.precioCliente < 0 || Number.isNaN(producto.precioCliente)) + ) { + return { + error: 'precioCliente debe ser un número mayor o igual a cero', + }; + } - for (const campo of camposNumericos) { - const valor = producto[campo] !== null ? Number(producto[campo]) : null; + if ( + !producto.precioPuntos + && (typeof producto.precioPuntos !== 'number' || producto.precioPuntos < 0 || Number.isNaN(producto.precioPuntos)) + ) { + return { + error: 'precioPuntos debe ser un número mayor o igual a cero', + }; + } if ( - valor !== null - && (Number.isNaN(valor) - || (campo === 'precioPuntos' && !Number.isInteger(valor)) - || (campo !== 'precioPuntos' && valor < 0)) + !producto.impuesto + && (typeof producto.impuesto !== 'number' || producto.impuesto < 0 || Number.isNaN(producto.impuesto)) ) { - return { error: `${campo} debe ser un número válido y mayor o igual a cero.` }; + return { + error: 'impuesto debe ser un número mayor o igual a cero', + }; } -} + if ( + !producto.descuento + && (typeof producto.descuento !== 'number' || producto.descuento < 0 || Number.isNaN(producto.descuento)) + ) { + return { + error: 'descuento debe ser un número mayor o igual a cero', + }; + } if (producto.estado !== undefined && producto.estado !== 0 && producto.estado !== 1) { return { error: 'estado debe ser 0 (inactivo) o 1 (activo).' }; From 0ac892824b8acbb05c36a9de986308e7329ad8fd Mon Sep 17 00:00:00 2001 From: NicoH00d Date: Mon, 2 Jun 2025 11:27:43 -0600 Subject: [PATCH 047/116] feat: agregar mas validaciones para importar --- .../Validaciones/validarProductoImportado.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Utilidades/Intermediarios/Validaciones/validarProductoImportado.js b/Utilidades/Intermediarios/Validaciones/validarProductoImportado.js index eaf26917..160306ba 100644 --- a/Utilidades/Intermediarios/Validaciones/validarProductoImportado.js +++ b/Utilidades/Intermediarios/Validaciones/validarProductoImportado.js @@ -61,16 +61,17 @@ module.exports = (producto) => { || producto.nombreComun.length > 100 ) { return { - error: 'nombreComun es requerido, debe ser una cadena de texto y no exceder 100 caracteres.', + error: 'nombreProducto es requerido, debe ser una cadena de texto y no exceder 100 caracteres.', }; } if ( - producto.nombreComercial !== null - && (typeof producto.nombreComercial !== 'string' || producto.nombreComercial.length > 150) + !producto.nombreComercial + || typeof producto.nombreComercial !== 'string' + || producto.nombreComercial.length > 100 ) { return { - error: 'nombreComercial debe ser una cadena de texto o NULL y no exceder 150 caracteres.', + error: 'nombreComercial es requerido, debe ser una cadena de texto y no exceder 100 caracteres.', }; } @@ -79,7 +80,7 @@ module.exports = (producto) => { && (typeof producto.descripcion !== 'string' || producto.descripcion.length > 1000) ) { return { - error: 'descripcion debe ser una cadena de texto o NULL y no exceder 1000 caracteres.', + error: 'descripcion debe ser una cadena de texto y no exceder 1000 caracteres.', }; } From 646d1b162adf04dca9b7546f060487f2b8b43104 Mon Sep 17 00:00:00 2001 From: NicoH00d Date: Mon, 2 Jun 2025 11:29:51 -0600 Subject: [PATCH 048/116] fix: cambios menores --- Eventos/Controladores/consultarEvento.controller.js | 1 + 1 file changed, 1 insertion(+) diff --git a/Eventos/Controladores/consultarEvento.controller.js b/Eventos/Controladores/consultarEvento.controller.js index d08c37c8..b7503273 100644 --- a/Eventos/Controladores/consultarEvento.controller.js +++ b/Eventos/Controladores/consultarEvento.controller.js @@ -12,6 +12,7 @@ const MENSAJES_EVENTOS = require('@altertex/util/const/mensajesEventos'); * @returns {Promise} Responde con el evento encontrado o un mensaje de error. * * @see [RF38 Leer evento](https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF38) + * */ exports.consultarEvento = async (req, res) => { const idEvento = parseInt(req.body.idEvento); From fb71e956534707bce6b804511de7ef873f9450db Mon Sep 17 00:00:00 2001 From: Diego Alfaro Pinto Date: Mon, 2 Jun 2025 12:19:53 -0600 Subject: [PATCH 049/116] fix: arreglar logica de actualizacion --- .idea/.gitignore | 8 + .idea/aws.xml | 17 ++ .idea/codeStyles/Project.xml | 60 ++++++ .idea/codeStyles/codeStyleConfig.xml | 5 + .idea/dataSources.xml | 12 ++ .idea/dictionaries/project.xml | 3 + .idea/inspectionProfiles/Project_Default.xml | 13 ++ .idea/material_theme_project_new.xml | 12 ++ .idea/prettier.xml | 8 + .idea/sqldialects.xml | 12 ++ .idea/vcs.xml | 6 + .idea/webResources.xml | 30 +++ .../repositorioActualizarGrupo.js | 99 +++++---- .../Constantes/consultasGrupoEmpleados.js | 203 ++++++++++-------- 14 files changed, 361 insertions(+), 127 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/aws.xml create mode 100644 .idea/codeStyles/Project.xml create mode 100644 .idea/codeStyles/codeStyleConfig.xml create mode 100644 .idea/dataSources.xml create mode 100644 .idea/dictionaries/project.xml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/material_theme_project_new.xml create mode 100644 .idea/prettier.xml create mode 100644 .idea/sqldialects.xml create mode 100644 .idea/vcs.xml create mode 100644 .idea/webResources.xml diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..13566b81 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/aws.xml b/.idea/aws.xml new file mode 100644 index 00000000..03f1bb6e --- /dev/null +++ b/.idea/aws.xml @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 00000000..6ea3fa32 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,60 @@ + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 00000000..79ee123c --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 00000000..913be6d2 --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,12 @@ + + + + + mysql.8 + true + com.mysql.cj.jdbc.Driver + jdbc:mysql://altertex.cqns2oecgotm.us-east-1.rds.amazonaws.com:3306/Altertex + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/.idea/dictionaries/project.xml b/.idea/dictionaries/project.xml new file mode 100644 index 00000000..47877842 --- /dev/null +++ b/.idea/dictionaries/project.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 00000000..26a57286 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,13 @@ + + + + \ No newline at end of file diff --git a/.idea/material_theme_project_new.xml b/.idea/material_theme_project_new.xml new file mode 100644 index 00000000..94763096 --- /dev/null +++ b/.idea/material_theme_project_new.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/.idea/prettier.xml b/.idea/prettier.xml new file mode 100644 index 00000000..60cfc994 --- /dev/null +++ b/.idea/prettier.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml new file mode 100644 index 00000000..bb491146 --- /dev/null +++ b/.idea/sqldialects.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..35eb1ddf --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/webResources.xml b/.idea/webResources.xml new file mode 100644 index 00000000..2d5e98e8 --- /dev/null +++ b/.idea/webResources.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Empleados/Datos/Repositorios/repositorioActualizarGrupo.js b/Empleados/Datos/Repositorios/repositorioActualizarGrupo.js index 0b9960bc..4f803667 100644 --- a/Empleados/Datos/Repositorios/repositorioActualizarGrupo.js +++ b/Empleados/Datos/Repositorios/repositorioActualizarGrupo.js @@ -15,20 +15,23 @@ const MENSAJES = require('@altertex/util/const/mensajesGrupoEmpleados'); * En caso de que los empleados o sets proporcionados no pertenezcan al mismo cliente, * se lanza un error. * + * Nota: Si empleados o setsDeProductos están vacíos, se eliminan todas las asociaciones existentes. + * * @async * @function actualizarGrupoEmpleados * @param {object} datosActualizacion - Datos necesarios para actualizar el grupo. * @param {number} datosActualizacion.idGrupoEmpleado - ID del grupo de empleados a actualizar. * @param {string} datosActualizacion.nombre - Nuevo nombre del grupo. * @param {string} datosActualizacion.descripcion - Nueva descripción del grupo. - * @param {number[]} datosActualizacion.empleados - Lista de IDs de empleados a asociar al grupo. - * @param {number[]} datosActualizacion.setsDeProductos - Lista de IDs de sets de productos a asociar al grupo. + * @param {number[]} datosActualizacion.empleados - Lista de IDs de empleados a asociar al grupo. Array vacío elimina todas las asociaciones. + * @param {number[]} datosActualizacion.setsDeProductos - Lista de IDs de sets de productos a asociar al grupo. Array vacío elimina todas las asociaciones. * @throws {Error} Si ocurre algún error durante la actualización o verificación de empleados/sets. */ exports.actualizarGrupoEmpleados = async (datosActualizacion) => { const { idGrupoEmpleado, nombre, descripcion, empleados, setsDeProductos } = datosActualizacion; try { + // Actualizar nombre y descripción del grupo await correrQuery(CONSULTAS.ACTUALIZAR_GRUPO_EMPLEADOS_NOMBRE_DESCRIPCION, [ nombre, descripcion, @@ -37,52 +40,72 @@ exports.actualizarGrupoEmpleados = async (datosActualizacion) => { descripcion, ]); - if (empleados.length > 0) { - const empleadosSTR = empleados.join(', '); - const valores = empleados - .map((empleadoId) => `(${empleadoId}, ${idGrupoEmpleado})`) - .join(', '); + // Manejar empleados (incluyendo arrays vacíos) + if (empleados !== undefined && Array.isArray(empleados)) { + if (empleados.length > 0) { + // Si hay empleados, verificar que pertenezcan al mismo cliente + const empleadosSTR = empleados.join(', '); + const valores = empleados + .map((empleadoId) => `(${empleadoId}, ${idGrupoEmpleado})`) + .join(', '); + + const resultadoVerificacion = await correrQuery( + CONSULTAS.VERIFICAR_EMPLEADOS_CLIENTE.replace('__EMPLEADOS__', empleadosSTR), + [idGrupoEmpleado], + ); + + if (resultadoVerificacion[0].validos !== empleados.length) { + throw new Error(MENSAJES.ERROR_VERIFICACION_CLIENTE_EMPLEADO.mensaje); + } - const resultadoVerificacion = await correrQuery( - CONSULTAS.VERIFICAR_EMPLEADOS_CLIENTE.replace('__EMPLEADOS__', empleadosSTR), - [idGrupoEmpleado] - ); + // Eliminar empleados que no están en la nueva lista + await correrQuery( + CONSULTAS.ELIMINAR_EMPLEADOS_DE_GRUPO_BASE.replace('__ID__', idGrupoEmpleado).replace( + '__EMPLEADOS__', + empleadosSTR, + ), + ); - if (resultadoVerificacion[0].validos !== empleados.length) { - throw new Error(MENSAJES.ERROR_VERIFICACION_CLIENTE_EMPLEADO.mensaje); + // Agregar los nuevos empleados + await correrQuery(CONSULTAS.AGREGAR_EMPLEADOS_NUEVOS_BASE.replace('__VALORES__', valores)); + } else { + // Si el array está vacío, eliminar todas las asociaciones de empleados + await correrQuery(CONSULTAS.ELIMINAR_TODOS_EMPLEADOS_DE_GRUPO, [idGrupoEmpleado]); } - await correrQuery( - CONSULTAS.ELIMINAR_EMPLEADOS_DE_GRUPO_BASE.replace('__ID__', idGrupoEmpleado).replace( - '__EMPLEADOS__', - empleadosSTR - ) - ); - await correrQuery(CONSULTAS.AGREGAR_EMPLEADOS_NUEVOS_BASE.replace('__VALORES__', valores)); } - if (setsDeProductos.length > 0) { - const setsSTR = setsDeProductos.join(', '); - const valores = setsDeProductos.map((setId) => `(${setId}, ${idGrupoEmpleado})`).join(', '); + // Manejar sets de productos (incluyendo arrays vacíos) + if (setsDeProductos !== undefined && Array.isArray(setsDeProductos)) { + if (setsDeProductos.length > 0) { + // Si hay sets, verificar que pertenezcan al mismo cliente + const setsSTR = setsDeProductos.join(', '); + const valores = setsDeProductos.map((setId) => `(${setId}, ${idGrupoEmpleado})`).join(', '); - const resultadoVerificacion = await correrQuery( - CONSULTAS.VERIFICAR_SETS_CLIENTE.replace('__SETS__', setsSTR), - [idGrupoEmpleado] - ); + const resultadoVerificacion = await correrQuery( + CONSULTAS.VERIFICAR_SETS_CLIENTE.replace('__SETS__', setsSTR), + [idGrupoEmpleado], + ); - if (resultadoVerificacion[0].validos !== setsDeProductos.length) { - throw new Error(MENSAJES.ERROR_VERIFICACION_CLIENTE_SET.mensaje); - } + if (resultadoVerificacion[0].validos !== setsDeProductos.length) { + throw new Error(MENSAJES.ERROR_VERIFICACION_CLIENTE_SET.mensaje); + } - await correrQuery( - CONSULTAS.ELIMINAR_SETS_DE_GRUPO_BASE.replace('__ID__', idGrupoEmpleado).replace( - '__SETS__', - setsSTR - ) - ); + // Eliminar sets que no están en la nueva lista + await correrQuery( + CONSULTAS.ELIMINAR_SETS_DE_GRUPO_BASE.replace('__ID__', idGrupoEmpleado).replace( + '__SETS__', + setsSTR, + ), + ); - await correrQuery(CONSULTAS.AGREGAR_SETS_NUEVOS_BASE.replace('__VALORES__', valores)); + // Agregar los nuevos sets + await correrQuery(CONSULTAS.AGREGAR_SETS_NUEVOS_BASE.replace('__VALORES__', valores)); + } else { + // Si el array está vacío, eliminar todas las asociaciones de sets + await correrQuery(CONSULTAS.ELIMINAR_TODOS_SETS_DE_GRUPO, [idGrupoEmpleado]); + } } } catch (error) { throw new Error(error); } -}; +}; \ No newline at end of file diff --git a/Utilidades/Constantes/consultasGrupoEmpleados.js b/Utilidades/Constantes/consultasGrupoEmpleados.js index 2a8de6e4..ffdd2b33 100644 --- a/Utilidades/Constantes/consultasGrupoEmpleados.js +++ b/Utilidades/Constantes/consultasGrupoEmpleados.js @@ -1,120 +1,145 @@ module.exports = { OBTENER_LISTA: ` - SELECT - ge.idGrupo, - ge.nombre AS geNombre, - ge.descripcion, - COUNT(eg.idEmpleado) AS totalEmpleados + SELECT ge.idGrupo, + ge.nombre AS geNombre, + ge.descripcion, + COUNT(eg.idEmpleado) AS totalEmpleados FROM grupo_empleado ge - LEFT JOIN empleado_grupo eg ON ge.idGrupo = eg.idGrupo + LEFT JOIN empleado_grupo eg ON ge.idGrupo = eg.idGrupo WHERE ge.idCliente = ? GROUP BY ge.idGrupo; - `, + `, ELIMINAR_SET_PRODUCTO_GRUPO: ` - DELETE FROM set_producto_grupo_empleado WHERE idGrupo = ?; + DELETE + FROM set_producto_grupo_empleado + WHERE idGrupo = ?; `, ELIMINAR_GRUPO: ` - DELETE FROM grupo_empleado WHERE idGrupo = ?; - `, + DELETE + FROM grupo_empleado + WHERE idGrupo = ?; + `, ELIMINAR_EMPLEADO_GRUPO: ` - DELETE FROM empleado_grupo WHERE idGrupo = ?; - `, + DELETE + FROM empleado_grupo + WHERE idGrupo = ?; + `, LEER_GRUPO: ` - SELECT - ge.idGrupo, - ge.nombre AS nombre, - ge.descripcion AS descripcion, + SELECT ge.idGrupo, + ge.nombre AS nombre, + ge.descripcion AS descripcion, - IFNULL(GROUP_CONCAT(DISTINCT sp.nombre SEPARATOR ', '), 'Sin sets de productos asociados') AS setsProductos, - IFNULL(GROUP_CONCAT(DISTINCT sp.idSetProducto SEPARATOR ','), '') AS idsSetProductos, + IFNULL(GROUP_CONCAT(DISTINCT sp.nombre SEPARATOR ', '), 'Sin sets de productos asociados') AS setsProductos, + IFNULL(GROUP_CONCAT(DISTINCT sp.idSetProducto SEPARATOR ','), '') AS idsSetProductos, - IFNULL(GROUP_CONCAT(DISTINCT CONCAT( + IFNULL(GROUP_CONCAT(DISTINCT CONCAT( u.nombreCompleto, ' | ', u.correoElectronico, ' | ', e.areaTrabajo - ) SEPARATOR ' || '), 'Sin empleados asociados') AS infoEmpleados, - - IFNULL(GROUP_CONCAT(DISTINCT e.idEmpleado SEPARATOR ','), '') AS idsEmpleados, - IFNULL( - JSON_ARRAYAGG( - JSON_OBJECT( - 'id', e.idEmpleado, - 'correo', u.correoElectronico, - 'nombre', u.nombreCompleto, - 'area', e.areaTrabajo - ) - ), - JSON_ARRAY() - ) AS empleadosActualizar, - IFNULL( - JSON_ARRAYAGG( - JSON_OBJECT( - 'id', sp.idSetProducto, - 'nombreProducto', sp.nombre, - 'activo', sp.activo - ) - ), - JSON_ARRAY() - ) AS setProductosActualizar - FROM grupo_empleado ge - LEFT JOIN empleado_grupo eg ON ge.idGrupo = eg.idGrupo - LEFT JOIN empleado e ON eg.idEmpleado = e.idEmpleado - LEFT JOIN usuario u ON e.idUsuario = u.idUsuario - LEFT JOIN set_producto_grupo_empleado spge ON ge.idGrupo = spge.idGrupo - LEFT JOIN set_producto sp ON spge.idSetProducto = sp.idSetProducto - WHERE ge.idGrupo = ? - GROUP BY ge.idGrupo - ORDER BY ge.idGrupo; - `, + ) SEPARATOR ' || '), 'Sin empleados asociados') AS infoEmpleados, - VALIDAR_NOMBRE_REPETIDO: ` - SELECT 1 FROM grupo_empleado - WHERE idCliente = ? AND nombre = ? LIMIT 1 - `, - CREAR_GRUPO: ` - INSERT INTO grupo_empleado (idCliente, nombre, descripcion) VALUES (?, ?, ?); - `, - ASIGNAR_EMPLEADO_A_GRUPO: ` - INSERT INTO empleado_grupo (idEmpleado, idGrupo) VALUES (?, ?); + IFNULL(GROUP_CONCAT(DISTINCT e.idEmpleado SEPARATOR ','), '') AS idsEmpleados, + IFNULL( + JSON_ARRAYAGG( + JSON_OBJECT( + 'id', e.idEmpleado, + 'correo', u.correoElectronico, + 'nombre', u.nombreCompleto, + 'area', e.areaTrabajo + ) + ), + JSON_ARRAY() + ) AS empleadosActualizar, + IFNULL( + JSON_ARRAYAGG( + JSON_OBJECT( + 'id', sp.idSetProducto, + 'nombreProducto', sp.nombre, + 'activo', sp.activo + ) + ), + JSON_ARRAY() + ) AS setProductosActualizar + FROM grupo_empleado ge + LEFT JOIN empleado_grupo eg ON ge.idGrupo = eg.idGrupo + LEFT JOIN empleado e ON eg.idEmpleado = e.idEmpleado + LEFT JOIN usuario u ON e.idUsuario = u.idUsuario + LEFT JOIN set_producto_grupo_empleado spge ON ge.idGrupo = spge.idGrupo + LEFT JOIN set_producto sp ON spge.idSetProducto = sp.idSetProducto + WHERE ge.idGrupo = ? + GROUP BY ge.idGrupo + ORDER BY ge.idGrupo; `, ACTUALIZAR_GRUPO_EMPLEADOS_NOMBRE_DESCRIPCION: ` - UPDATE grupo_empleado - SET nombre = ?, descripcion = ? - WHERE idGrupo = ? - AND (nombre != ? OR descripcion != ?); - `, + UPDATE grupo_empleado + SET nombre = ?, + descripcion = ? + WHERE idGrupo = ? + AND (nombre != ? OR descripcion != ?); + `, ELIMINAR_EMPLEADOS_DE_GRUPO_BASE: ` - DELETE FROM empleado_grupo - WHERE idGrupo = __ID__ - AND idEmpleado NOT IN (__EMPLEADOS__); + DELETE + FROM empleado_grupo + WHERE idGrupo = __ID__ + AND idEmpleado NOT IN (__EMPLEADOS__); `, AGREGAR_EMPLEADOS_NUEVOS_BASE: ` - INSERT IGNORE INTO empleado_grupo (idEmpleado, idGrupo) + INSERT + IGNORE INTO empleado_grupo (idEmpleado, idGrupo) VALUES __VALORES__; `, VERIFICAR_EMPLEADOS_CLIENTE: ` - SELECT COUNT(*) AS validos - FROM empleado e - JOIN grupo_empleado g ON g.idGrupo = ? - WHERE e.idEmpleado IN (__EMPLEADOS__) - AND e.idCliente = g.idCliente + SELECT COUNT(*) AS validos + FROM empleado e + JOIN grupo_empleado g ON g.idGrupo = ? + WHERE e.idEmpleado IN (__EMPLEADOS__) + AND e.idCliente = g.idCliente `, VERIFICAR_SETS_CLIENTE: ` - SELECT COUNT(*) AS validos - FROM set_producto s - JOIN grupo_empleado g ON g.idGrupo = ? - WHERE s.idSetProducto IN (__SETS__) - AND s.idCliente = g.idCliente -`, + SELECT COUNT(*) AS validos + FROM set_producto s + JOIN grupo_empleado g ON g.idGrupo = ? + WHERE s.idSetProducto IN (__SETS__) + AND s.idCliente = g.idCliente + `, ELIMINAR_SETS_DE_GRUPO_BASE: ` - DELETE FROM set_producto_grupo_empleado - WHERE idGrupo = __ID__ - AND idSetProducto NOT IN (__SETS__); -`, + DELETE + FROM set_producto_grupo_empleado + WHERE idGrupo = __ID__ + AND idSetProducto NOT IN (__SETS__); + `, AGREGAR_SETS_NUEVOS_BASE: ` - INSERT IGNORE INTO set_producto_grupo_empleado (idSetProducto, idGrupo) + INSERT + IGNORE INTO set_producto_grupo_empleado (idSetProducto, idGrupo) VALUES __VALORES__; -`, -}; + `, + + VALIDAR_NOMBRE_REPETIDO: ` + SELECT 1 + FROM grupo_empleado + WHERE idCliente = ? + AND nombre = ? LIMIT 1 + `, + CREAR_GRUPO: ` + INSERT INTO grupo_empleado (idCliente, nombre, descripcion) + VALUES (?, ?, ?); + `, + ASIGNAR_EMPLEADO_A_GRUPO: ` + INSERT INTO empleado_grupo (idEmpleado, idGrupo) + VALUES (?, ?); + `, + + ELIMINAR_TODOS_EMPLEADOS_DE_GRUPO: ` + DELETE + FROM empleado_grupo + WHERE idGrupo = ?; + `, + + ELIMINAR_TODOS_SETS_DE_GRUPO: ` + DELETE + FROM set_producto_grupo_empleado + WHERE idGrupo = ?; + `, +}; \ No newline at end of file From 51c06e810e545c9c767988cb598aea9b1be0707a Mon Sep 17 00:00:00 2001 From: Krlos7121 Date: Mon, 2 Jun 2025 12:56:35 -0600 Subject: [PATCH 050/116] Merge branch 'develop' of https://github.com/CodeAnd-Co/Backend-textiles into feature/CIFM_RF16_Crear-empleado --- .../Controladores/crearEmpleado.controller.js | 151 +++++++----------- 1 file changed, 61 insertions(+), 90 deletions(-) diff --git a/Empleados/Controladores/crearEmpleado.controller.js b/Empleados/Controladores/crearEmpleado.controller.js index 57c32c2b..73dc5ac5 100644 --- a/Empleados/Controladores/crearEmpleado.controller.js +++ b/Empleados/Controladores/crearEmpleado.controller.js @@ -35,134 +35,105 @@ const repositorio = require('@altertex/emp/repos/repositorioCrearEmpleado'); * - 500 si ocurre un error al crear el empleado. */ exports.crearEmpleado = async (req, res) => { - const idCliente = parseInt(req.user.clienteSeleccionado); - const empleado = req.body; - - if (!empleado || typeof empleado !== 'object' || Array.isArray(empleado)) { - return res.status(400).json({ mensaje: MENSAJES.DATOS_INCOMPLETOS.mensaje }); - } - - const errores = []; - const { + const [ nombreCompleto, correoElectronico, - contrasena, - numeroTelefono, + contrasenia, + numberoTelefono, direccion, fechaNacimiento, genero, estatus, idRol, - idCliente: clienteId, + idCliente, numeroEmergencia, areaTrabajo, posicion, cantidadPuntos, antiguedad, - } = empleado; + ] = req.body; - // Validaciones de campos requeridos if ( + !Array.isArray(req.body) || + req.body.length === 0 || !nombreCompleto || !correoElectronico || - !contrasena || - !numeroTelefono || + !contrasenia || + !numberoTelefono || !direccion || !fechaNacimiento || !genero || estatus === undefined || - idRol === undefined || + !idRol || + idCliente === undefined || + (Array.isArray(idCliente) && idCliente.length === 0) || !numeroEmergencia || !areaTrabajo || !posicion || cantidadPuntos === undefined || !antiguedad ) { - return res.status(400).json({ mensaje: MENSAJES.DATOS_INCOMPLETOS.mensaje }); - } - - // Validaciones de longitud y formato - if (nombreCompleto.length > 75) { - errores.push({ campo: 'nombreCompleto', error: 'El nombre es demasiado largo' }); - } - if (correoElectronico.length > 75) { - errores.push({ campo: 'correoElectronico', error: 'El correo es demasiado largo' }); - } - if (contrasena.length > 75) { - errores.push({ campo: 'contrasena', error: 'La contraseña es demasiado larga' }); - } - if (direccion.length > 150) { - errores.push({ campo: 'direccion', error: 'La dirección es demasiado larga' }); - } - if (posicion.length > 75) { - errores.push({ campo: 'posicion', error: 'La posición es demasiado larga' }); - } - if (areaTrabajo.length > 75) { - errores.push({ campo: 'areaTrabajo', error: 'El área de trabajo es demasiado larga' }); - } - if (genero.length > 20) { - errores.push({ campo: 'genero', error: 'El género es demasiado largo' }); - } - - // Validación de estatus nulo - if (estatus == null) { - errores.push({ campo: 'estatus', error: 'Estatus inválido: debe ser 0 o 1' }); - } - - // Validación de idCliente (no debe venir en el body) - if (typeof clienteId !== 'undefined' && clienteId !== '' && clienteId !== null) { - errores.push({ campo: 'idCliente', error: 'El cliente no debe ser incluido en el archivo' }); + return res.status(400).json({ mensaje: 'Faltan campos requeridos' }); } - - // Validación de número de emergencia (numérico) - if (isNaN(numeroEmergencia)) { - errores.push({ campo: 'numeroEmergencia', error: 'El número de emergencia no es válido' }); - } - - // Validación de correo electrónico válido const correoValido = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!correoValido.test(correoElectronico)) { - errores.push({ campo: 'correoElectronico', error: 'El correo electrónico no es válido' }); + return res + .status(MENSAJES.CORREO_INVALIDO.codigo) + .json({ mensaje: MENSAJES.CORREO_INVALIDO.mensaje }); } - - // Validación de contraseña fuerte const tieneCaracterEspecial = /[!@#$%^&*(),.?":{}|<>]/; const tieneMayuscula = /[A-Z]/; - if ( - contrasena.length < 8 || - !tieneCaracterEspecial.test(contrasena) || - !tieneMayuscula.test(contrasena) - ) { - errores.push({ - campo: 'contrasena', - error: - 'La contraseña es débil. Debe tener al menos 8 caracteres, una mayúscula y un caracter especial.', - }); + if (contrasenia.length < 8) { + return res + .status(MENSAJES.CONTRASENA_DEBIL.codigo) + .json({ mensaje: MENSAJES.CONTRASENA_DEBIL.mensaje }); } - - // Validación de teléfono válido - const telefonoValido = /^\d{10}$/; - if (!telefonoValido.test(numeroTelefono)) { - errores.push({ - campo: 'numeroTelefono', - error: 'El número de teléfono debe tener 10 dígitos numéricos', - }); + if (!tieneCaracterEspecial.test(contrasenia)) { + return res + .status(MENSAJES.CONTRASENA_DEBIL.codigo) + .json({ mensaje: MENSAJES.CONTRASENA_DEBIL.mensaje }); + } + if (!tieneMayuscula.test(contrasenia)) { + return res + .status(MENSAJES.CONTRASENA_DEBIL.codigo) + .json({ mensaje: MENSAJES.CONTRASENA_DEBIL.mensaje }); } - if (errores.length > 0) { - return res.status(400).json({ errores }); + const telefonoValido = /^\d{10}$/; + if (!telefonoValido.test(numberoTelefono)) { + return res + .status(MENSAJES.TELEFONO_INVALIDO.codigo) + .json({ mensaje: MENSAJES.TELEFONO_INVALIDO.mensaje }); } try { - // Encriptar la contraseña antes de guardarla - const contrasenaEncriptada = await bcrypt.hash(contrasena, 10); - empleado.contrasena = contrasenaEncriptada; - - // Crear el empleado usando el repositorio - const nuevoEmpleado = await repositorio.crearEmpleado(empleado, idCliente); - return res.status(201).json(nuevoEmpleado); + const contraseniaEncriptada = await bcrypt.hash(contrasenia, 10); + const resultado = await repositorio.crearEmpleado( + nombreCompleto, + correoElectronico, + contraseniaEncriptada, + numberoTelefono, + direccion, + fechaNacimiento, + genero, + estatus, + idRol, + idCliente, + numeroEmergencia, + areaTrabajo, + posicion, + cantidadPuntos, + antiguedad + ); + return res.status(MENSAJES_EMPLEADOS.CREACION_EXITOSA.codigo).json({ + mensaje: MENSAJES.CREACION_EXITOSA.mensaje, + datos: resultado, + }); } catch (error) { - console.error('Error al crear el empleado:', error); - return res.status(500).json({ mensaje: MENSAJES.ERROR_CREAR.mensaje }); + console.error('Error al crear empleado:', error); + return res.status(MENSAJES.ERROR_CREACION.codigo).json({ + mensaje: MENSAJES.ERROR_CREACION.mensaje, + error: error.message, + }); } }; From bf0db1f142acd37159258a8d283c6dfd8c29abb0 Mon Sep 17 00:00:00 2001 From: NicoH00d Date: Mon, 2 Jun 2025 15:07:55 -0600 Subject: [PATCH 051/116] fix: agregar validaciones adicionales --- .../importarProductos.controller.js | 6 +-- .../Validaciones/validarOpcionesImportar.js | 4 +- .../Validaciones/validarProductoImportado.js | 49 ++++++++++++------- 3 files changed, 36 insertions(+), 23 deletions(-) diff --git a/Productos/Controladores/importarProductos.controller.js b/Productos/Controladores/importarProductos.controller.js index a0594a9b..2735b571 100644 --- a/Productos/Controladores/importarProductos.controller.js +++ b/Productos/Controladores/importarProductos.controller.js @@ -85,18 +85,18 @@ exports.importarProductos = async (req, res) => { errores, }); } - + // Si no hubo errores, insertar todos los productos for (let im = 0; im < productos.length; im += 1) { const { producto, variantes } = productos[im]; const idProducto = await repositorioCrearProducto.crearProducto(idCliente, producto); - + for (const variante of variantes) { const idVariante = await repositorioCrearVariante.crearVariante(idProducto, variante); await repositorioCrearOpcion.crearOpcion(idVariante, variante.opciones); } } - + await conexion.commit(); return res.status(200).json({ diff --git a/Utilidades/Intermediarios/Validaciones/validarOpcionesImportar.js b/Utilidades/Intermediarios/Validaciones/validarOpcionesImportar.js index 5163c279..6be7ce7e 100644 --- a/Utilidades/Intermediarios/Validaciones/validarOpcionesImportar.js +++ b/Utilidades/Intermediarios/Validaciones/validarOpcionesImportar.js @@ -39,6 +39,7 @@ module.exports = (opciones) => { typeof opcion.cantidad !== 'number' || opcion.cantidad < 0 || !Number.isInteger(opcion.cantidad) + || opcion.cantidad % 1 !== 0 ) { return { error: 'cantidad de la opción debe ser un número entero positivo o cero.' }; } @@ -63,7 +64,8 @@ module.exports = (opciones) => { return { error: 'SKUautomatico es requerido y debe ser una cadena de texto de máximo 50 caracteres.' }; } // prettier-ignore - if (opcion.SKUcomercial == null + if (!opcion.SKUcomercial + || opcion.SKUcomercial == null || typeof opcion.SKUcomercial !== 'string' || opcion.SKUcomercial.length > 50 ) { diff --git a/Utilidades/Intermediarios/Validaciones/validarProductoImportado.js b/Utilidades/Intermediarios/Validaciones/validarProductoImportado.js index 160306ba..9d542a2e 100644 --- a/Utilidades/Intermediarios/Validaciones/validarProductoImportado.js +++ b/Utilidades/Intermediarios/Validaciones/validarProductoImportado.js @@ -48,13 +48,17 @@ module.exports = (producto) => { if ( producto.idProveedor !== null - && (typeof producto.idProveedor !== 'number' + && ( + typeof producto.idProveedor !== 'number' || producto.idProveedor <= 0 - || !Number.isInteger(producto.idProveedor)) + || producto.idProveedor % 1 !== 0 + ) ) { return { error: 'idProveedor debe ser un número entero positivo o NULL.' }; } + + if ( !producto.nombreComun || typeof producto.nombreComun !== 'string' @@ -77,7 +81,7 @@ module.exports = (producto) => { if ( producto.descripcion !== null - && (typeof producto.descripcion !== 'string' || producto.descripcion.length > 1000) + && (typeof producto.descripcion !== 'string' || producto.descripcion.length > 1000 || producto.descripcion.trim() === '') ) { return { error: 'descripcion debe ser una cadena de texto y no exceder 1000 caracteres.', @@ -86,21 +90,21 @@ module.exports = (producto) => { if ( producto.marca !== null - && (typeof producto.marca !== 'string' || producto.marca.length > 100) + && (typeof producto.marca !== 'string' || producto.marca.length > 100 || producto.marca.trim() === '') ) { return { error: 'marca debe ser una cadena de texto o NULL y no exceder 100 caracteres.' }; } if ( producto.modelo !== null - && (typeof producto.modelo !== 'string' || producto.modelo.length > 100) + && (typeof producto.modelo !== 'string' || producto.modelo.length > 100 || producto.modelo.trim() === '') ) { return { error: 'modelo debe ser una cadena de texto o NULL y no exceder 100 caracteres.' }; } if ( producto.tipoProducto !== null - && (typeof producto.tipoProducto !== 'string' || producto.tipoProducto.length > 50) + && (typeof producto.tipoProducto !== 'string' || producto.tipoProducto.length > 50 || producto.tipoProducto.trim() === '') ) { return { error: 'tipoProducto debe ser una cadena de texto o NULL y no exceder 50 caracteres.', @@ -108,16 +112,19 @@ module.exports = (producto) => { } if ( - !producto.costo - && (typeof producto.costo !== 'number' || producto.costo < 0 || Number.isNaN(producto.costo)) + typeof producto.costo !== 'number' + || producto.costo < 0 + || Number.isNaN(producto.costo) ) { return { error: 'costo debe ser un número mayor o igual a cero', }; } + if ( - !producto.precioVenta - && (typeof producto.precioVenta !== 'number' || producto.precioVenta < 0 || Number.isNaN(producto.precioVenta)) + typeof producto.precioVenta !== 'number' + || producto.precioVenta < 0 + || Number.isNaN(producto.precioVenta) ) { return { error: 'precioVenta debe ser un número mayor o igual a cero', @@ -125,8 +132,9 @@ module.exports = (producto) => { } if ( - !producto.precioCliente - && (typeof producto.precioCliente !== 'number' || producto.precioCliente < 0 || Number.isNaN(producto.precioCliente)) + typeof producto.precioCliente !== 'number' + || producto.precioCliente < 0 + || Number.isNaN(producto.precioCliente) ) { return { error: 'precioCliente debe ser un número mayor o igual a cero', @@ -134,8 +142,9 @@ module.exports = (producto) => { } if ( - !producto.precioPuntos - && (typeof producto.precioPuntos !== 'number' || producto.precioPuntos < 0 || Number.isNaN(producto.precioPuntos)) + typeof producto.precioPuntos !== 'number' + || producto.precioPuntos < 0 + || Number.isNaN(producto.precioPuntos) ) { return { error: 'precioPuntos debe ser un número mayor o igual a cero', @@ -143,8 +152,9 @@ module.exports = (producto) => { } if ( - !producto.impuesto - && (typeof producto.impuesto !== 'number' || producto.impuesto < 0 || Number.isNaN(producto.impuesto)) + typeof producto.impuesto !== 'number' + || producto.impuesto < 0 + || Number.isNaN(producto.impuesto) ) { return { error: 'impuesto debe ser un número mayor o igual a cero', @@ -152,11 +162,12 @@ module.exports = (producto) => { } if ( - !producto.descuento - && (typeof producto.descuento !== 'number' || producto.descuento < 0 || Number.isNaN(producto.descuento)) + typeof producto.descuento !== 'number' + || producto.descuento < 0 || producto.descuento > 100 + || Number.isNaN(producto.descuento) ) { return { - error: 'descuento debe ser un número mayor o igual a cero', + error: 'descuento debe ser un número mayor o igual a cero y menor o igual a 100', }; } From 11f1837333303ac82a00983b4deab5a12d49a3bc Mon Sep 17 00:00:00 2001 From: NicoH00d Date: Mon, 2 Jun 2025 18:13:40 -0600 Subject: [PATCH 052/116] fix: actualizar documentacionn --- .../importarProductos.controller.js | 7 +- .../importarProductos.routes.js | 170 +++++++++++++++++- Productos/Rutas/indexProductos.routes.js | 2 +- .../Validaciones/validarProductoImportado.js | 2 - 4 files changed, 171 insertions(+), 10 deletions(-) diff --git a/Productos/Controladores/importarProductos.controller.js b/Productos/Controladores/importarProductos.controller.js index 2735b571..ffb79f02 100644 --- a/Productos/Controladores/importarProductos.controller.js +++ b/Productos/Controladores/importarProductos.controller.js @@ -6,7 +6,6 @@ const repositorioCrearOpcion = require('@altertex/pro/repos/repositorioCrearOpci const db = require('@altertex/util/bd/db'); const validarProductoImportado = require('@altertex/util/vali/validarProductoImportado'); - /** * Importa productos y sus variantes/opciones para un cliente. * @@ -31,10 +30,12 @@ const validarProductoImportado = require('@altertex/util/vali/validarProductoImp * @param {Express.Request} req - Request de Express, requiere req.user.clienteSeleccionado y req.body. * @param { Express.Response} res - Response de Express. * @returns {Promise} Devuelve un JSON con el resultado de la importación y los errores encontrados. + * + * @see RF[56] Leer producto - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF56] */ exports.importarProductos = async (req, res) => { const idCliente = parseInt(req.user.clienteSeleccionado); - const productos = req.body; // Espera array de { producto, variantes } + const productos = req.body; if (!Array.isArray(productos) || productos.length === 0) { return res.status(400).json({ mensaje: 'No se recibieron productos válidos.' }); @@ -47,7 +48,6 @@ exports.importarProductos = async (req, res) => { conexion = await db.getConnection(); await conexion.beginTransaction(); - // Validación previa de todos los productos for (let im = 0; im < productos.length; im += 1) { const { producto, variantes } = productos[im]; const fila = im + 1; @@ -86,7 +86,6 @@ exports.importarProductos = async (req, res) => { }); } - // Si no hubo errores, insertar todos los productos for (let im = 0; im < productos.length; im += 1) { const { producto, variantes } = productos[im]; const idProducto = await repositorioCrearProducto.crearProducto(idCliente, producto); diff --git a/Productos/Rutas/RutasIndividuales/importarProductos.routes.js b/Productos/Rutas/RutasIndividuales/importarProductos.routes.js index f5d18b25..2c95b954 100644 --- a/Productos/Rutas/RutasIndividuales/importarProductos.routes.js +++ b/Productos/Rutas/RutasIndividuales/importarProductos.routes.js @@ -1,4 +1,4 @@ -//RF26 Crea Producto - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF26 +// RF[56] Leer producto - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF56] const express = require('express'); const ruteador = express.Router(); const controlador = require('@altertex/pro/ctrl/importarProductos.controller'); @@ -6,20 +6,184 @@ const revisarApiKey = require('@altertex/util/inter/revisarApiKey'); const autorizarToken = require('@altertex/util/inter/autorizarToken'); const verificarPermisos = require('@altertex/util/inter/verificarPermisos'); const limitePeticionesDiarias = require('@altertex/util/inter/limitePeticiones'); - +const validarYSanitizar = require('@altertex/util/inter/validarYSanitizar'); const PERMISOS = require('@altertex/util/const/permisos'); const RUTAS = require('@altertex/util/const/rutas'); /** * @swagger -**/ + * /api/productos/importar-productos: + * post: + * summary: Importa un lote de productos con variantes y opciones desde un archivo procesado. + * tags: + * - Productos + * security: + * - bearerAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: array + * items: + * type: object + * required: + * - producto + * - variantes + * properties: + * producto: + * type: object + * properties: + * idProveedor: + * type: integer + * nullable: true + * example: 12 + * nombreComun: + * type: string + * example: Tornillo galvanizado + * nombreComercial: + * type: string + * nullable: true + * example: Tornillo FG 3/4 + * descripcion: + * type: string + * nullable: true + * example: Tornillo de acero inoxidable + * marca: + * type: string + * nullable: true + * example: TRUPER + * modelo: + * type: string + * nullable: true + * example: M3X40 + * tipoProducto: + * type: string + * nullable: true + * example: Ferretería + * costo: + * type: number + * example: 3.5 + * precioVenta: + * type: number + * example: 5.0 + * precioCliente: + * type: number + * example: 4.5 + * precioPuntos: + * type: number + * example: 4.0 + * impuesto: + * type: number + * example: 0.16 + * descuento: + * type: number + * example: 0.1 + * estado: + * type: integer + * enum: [0, 1] + * example: 1 + * envio: + * type: integer + * enum: [0, 1] + * example: 1 + * variantes: + * type: array + * items: + * type: object + * required: + * - nombreVariante + * - opciones + * properties: + * nombreVariante: + * type: string + * example: Color + * descripcion: + * type: string + * nullable: true + * example: Variantes por color + * opciones: + * type: array + * items: + * type: object + * required: + * - cantidad + * - valorOpcion + * - SKUautomatico + * - SKUcomercial + * - costoAdicional + * - descuento + * - estado + * properties: + * cantidad: + * type: integer + * example: 10 + * valorOpcion: + * type: string + * example: Rojo + * SKUautomatico: + * type: string + * example: SKU-AUTO-1 + * SKUcomercial: + * type: string + * example: SKU-ROJO-123 + * costoAdicional: + * type: number + * example: 0.5 + * descuento: + * type: number + * example: 10 + * estado: + * type: integer + * enum: [0, 1] + * example: 1 + * responses: + * 200: + * description: Importación completada exitosamente o con errores parciales. + * content: + * application/json: + * schema: + * type: object + * properties: + * mensaje: + * type: string + * example: Importación completada exitosamente. + * errores: + * type: array + * items: + * type: object + * properties: + * fila: + * type: integer + * example: 3 + * error: + * type: string + * example: Producto sin variantes válidas. + * 400: + * description: No se recibieron productos válidos. + * content: + * application/json: + * schema: + * type: object + * properties: + * mensaje: + * type: string + * example: No se recibieron productos válidos. + * 401: + * description: No autorizado. Token inválido o faltante. + * 403: + * description: Acceso denegado por falta de permisos. + * 500: + * description: Error interno del servidor. + */ ruteador.post( RUTAS.PRODUCTOS.IMPORTAR, revisarApiKey(), autorizarToken, limitePeticionesDiarias, + validarYSanitizar, verificarPermisos(PERMISOS.IMPORTAR_PRODUCTOS), controlador.importarProductos ); diff --git a/Productos/Rutas/indexProductos.routes.js b/Productos/Rutas/indexProductos.routes.js index 3c8723d9..e451e72a 100644 --- a/Productos/Rutas/indexProductos.routes.js +++ b/Productos/Rutas/indexProductos.routes.js @@ -16,7 +16,7 @@ ruteador.use(RUTAS.PRODUCTOS.BASE, rutaCrearProducto); ruteador.use(RUTAS.PRODUCTOS.BASE, rutaEliminar); // RF[28] Leer producto - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF28] ruteador.use(RUTAS.PRODUCTOS.BASE, rutasLeerProducto); - +// RF[56] Leer producto - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF56] ruteador.use(RUTAS.PRODUCTOS.BASE, rutaImportarProductos); module.exports = ruteador; \ No newline at end of file diff --git a/Utilidades/Intermediarios/Validaciones/validarProductoImportado.js b/Utilidades/Intermediarios/Validaciones/validarProductoImportado.js index 9d542a2e..791ab927 100644 --- a/Utilidades/Intermediarios/Validaciones/validarProductoImportado.js +++ b/Utilidades/Intermediarios/Validaciones/validarProductoImportado.js @@ -57,8 +57,6 @@ module.exports = (producto) => { return { error: 'idProveedor debe ser un número entero positivo o NULL.' }; } - - if ( !producto.nombreComun || typeof producto.nombreComun !== 'string' From fa42f8de7f7b365ed2b6d15eb2ba2f73ca7c749d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valeria=20Zu=C3=B1iga=20Mendoza?= Date: Tue, 3 Jun 2025 10:26:49 -0600 Subject: [PATCH 053/116] chore: Agregar rama para GitHub Actions --- .github/workflows/on-pr.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/on-pr.yaml b/.github/workflows/on-pr.yaml index 009150e8..8f4801c2 100644 --- a/.github/workflows/on-pr.yaml +++ b/.github/workflows/on-pr.yaml @@ -6,6 +6,7 @@ on: - main - staging - develop + - MBI-1 jobs: lint: @@ -18,7 +19,7 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v4 with: - node-version: "22.14" + node-version: '22.14' - name: Install dependencies run: npm install @@ -35,7 +36,7 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v4 with: - node-version: "22.14" + node-version: '22.14' - name: Install dependencies run: npm install From bc1a2ae90e47af7fbb72b47f7383cebfe2e2c89a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Antonio=20Ben=C3=ADtez=20De=20La=20Portilla?= Date: Tue, 3 Jun 2025 11:32:03 -0600 Subject: [PATCH 054/116] =?UTF-8?q?feat:=20actualic=C3=A9=20para=20que=20t?= =?UTF-8?q?enga=202=20querys=20el=20repositorio=20uno=20para=20el=20nombre?= =?UTF-8?q?=20y=20descripci=C3=B3n=20y=20otro=20para=20los=20productos=20y?= =?UTF-8?q?=20las=20cuotas=20de=20los=20productos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cuotas/Controladores/leerSetCuotas.controller.js | 2 +- .../Repositorios/leerSetCuotasRepositorio.js | 15 +++++++++++---- Utilidades/Constantes/consultasCuotas.js | 8 ++++---- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/Cuotas/Controladores/leerSetCuotas.controller.js b/Cuotas/Controladores/leerSetCuotas.controller.js index 768f72a1..9c33418d 100644 --- a/Cuotas/Controladores/leerSetCuotas.controller.js +++ b/Cuotas/Controladores/leerSetCuotas.controller.js @@ -11,7 +11,7 @@ const MENSAJES_CUOTAS = require('@altertex/util/const/mensajesCuotas'); * @param {Express.Response} res - La respuesta HTTP para enviar el resultado al cliente. * @returns {Promise} Responde con el set de cuotas encontrado o un mensaje de error. * - * @see [RF33 Leer set cuotas](https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF33) + * @see [RF33] Leer set cuotas(https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF33) */ exports.leerSetCuotas = async (req, res) => { const idSetCuota = parseInt(req.body.idSetCuota); diff --git a/Cuotas/Datos/Repositorios/leerSetCuotasRepositorio.js b/Cuotas/Datos/Repositorios/leerSetCuotasRepositorio.js index 59c03296..62a4c649 100644 --- a/Cuotas/Datos/Repositorios/leerSetCuotasRepositorio.js +++ b/Cuotas/Datos/Repositorios/leerSetCuotasRepositorio.js @@ -12,18 +12,25 @@ const CONSULTAS_CUOTAS = require('@altertex/util/const/consultasCuotas'); */ exports.obtenerSetCuotaPorId = async (idSetCuota) => { const query = CONSULTAS_CUOTAS.LEER_CUOTA_SET; + const query_cuotas = CONSULTAS_CUOTAS.LEER_CUOTA_SET_PRODUCTOS; const resultado = await correrQuery(query, [idSetCuota]); - if (resultado.length === 0) return null; + const productos_cuota = await correrQuery(query_cuotas, [idSetCuota]); + const productos = productos_cuota.map((producto) => ({ + nombre: producto.nombreComun, + })); + const cuotas = productos_cuota.map((producto) => ({ + valor: producto.cuota_valor, + })); + const setCuota = { idSetCuota: resultado[0].idCuotaSet, nombre: resultado[0].nombre, descripcion: resultado[0].descripcion, - periodoRenovacion: resultado[0].periodoRenovacion, - renovacionHabilitada: resultado[0].renovacionHabilitada, - ultimaActualizacion: resultado[0].ultimaActualizacion, + productos: productos, + cuotas: cuotas, }; return setCuota; diff --git a/Utilidades/Constantes/consultasCuotas.js b/Utilidades/Constantes/consultasCuotas.js index 09a035c5..cc1d4465 100644 --- a/Utilidades/Constantes/consultasCuotas.js +++ b/Utilidades/Constantes/consultasCuotas.js @@ -55,12 +55,12 @@ module.exports = { LEER_CUOTA_SET_PRODUCTOS: ` SELECT p.nombreComun, - csp.limite AS cuota_valor + csp.limite AS cuota_valor FROM cuota_set cs - JOIN cuota_set_producto csp + JOIN cuota_set_producto csp ON cs.idCuotaSet = csp.idCuotaSet - JOIN producto p - ON p.idProducto = csp.idProducto + JOIN producto p + ON p.idProducto = csp.idProducto WHERE cs.idCuotaSet = ?; `, }; From 8bc0fb4a96c7837a1ea0c70db7240dbd0925d346 Mon Sep 17 00:00:00 2001 From: PAOLA MARIA garrido Date: Tue, 3 Jun 2025 12:08:52 -0600 Subject: [PATCH 055/116] fix: actualizar comentario de swagger --- .../exportarEmpleados.routes.js | 44 +++++-------------- 1 file changed, 12 insertions(+), 32 deletions(-) diff --git a/Empleados/Rutas/RutasIndividuales/exportarEmpleados.routes.js b/Empleados/Rutas/RutasIndividuales/exportarEmpleados.routes.js index ae14f0dc..2d4d0b8a 100644 --- a/Empleados/Rutas/RutasIndividuales/exportarEmpleados.routes.js +++ b/Empleados/Rutas/RutasIndividuales/exportarEmpleados.routes.js @@ -1,20 +1,9 @@ //RF59 - Exportar Empleados- https://codeandco-wiki.netlify.app/docs/next/proyectos/textiles/documentacion/requisitos/RF59 -const express = require('express'); -const ruteador = express.Router(); -const controlador = require('@altertex/emp/ctrl/exportarEmpleados.controller'); -const revisarApiKey = require('@altertex/util/inter/revisarApiKey'); -const autorizarToken = require('@altertex/util/inter/autorizarToken'); -const verificarPermisos = require('@altertex/util/inter/verificarPermisos'); -const PERMISOS = require('@altertex/util/const/permisos'); -const RUTAS = require('@altertex/util/const/rutas'); -const validarYSanitizar = require('@altertex/util/inter/validarYSanitizar'); -const limitePeticionesDiarias = require('@altertex/util/inter/limitePeticiones'); - /** * @swagger * /api/empleados/exportar: - * get: + * post: * summary: Exporta la lista completa de empleados en formato CSV. * tags: * - Empleados @@ -52,26 +41,6 @@ const limitePeticionesDiarias = require('@altertex/util/inter/limitePeticiones') * mensaje: * type: string * example: No hay empleados para exportar. - * 400: - * description: Clave de API inválida o ausente. - * content: - * application/json: - * schema: - * type: object - * properties: - * mensaje: - * type: string - * example: API key inválida. - * 401: - * description: Usuario no autenticado o token JWT inválido. - * content: - * application/json: - * schema: - * type: object - * properties: - * mensaje: - * type: string - * example: No autorizado. * 500: * description: Error interno del servidor al exportar empleados. * content: @@ -84,6 +53,17 @@ const limitePeticionesDiarias = require('@altertex/util/inter/limitePeticiones') * example: Error al exportar la lista de empleados. */ +const express = require('express'); +const ruteador = express.Router(); +const controlador = require('@altertex/emp/ctrl/exportarEmpleados.controller'); +const revisarApiKey = require('@altertex/util/inter/revisarApiKey'); +const autorizarToken = require('@altertex/util/inter/autorizarToken'); +const verificarPermisos = require('@altertex/util/inter/verificarPermisos'); +const PERMISOS = require('@altertex/util/const/permisos'); +const RUTAS = require('@altertex/util/const/rutas'); +const validarYSanitizar = require('@altertex/util/inter/validarYSanitizar'); +const limitePeticionesDiarias = require('@altertex/util/inter/limitePeticiones'); + ruteador.post( RUTAS.EMPLEADOS.EXPORTAR_EMPLEADOS, revisarApiKey(), From 5c04f59f1a48f0d4f130e0855d97539194349b0c Mon Sep 17 00:00:00 2001 From: max Date: Tue, 3 Jun 2025 13:30:00 -0600 Subject: [PATCH 056/116] feat(usuarios): enhance user update functionality and improve user list response formatting --- .../consultarListaUsuarios.controller.js | 13 +++++- .../repositorioActualizarUsuario.js | 29 +++++++++++-- Utilidades/Constantes/consultasUsuarios.js | 41 +++++++++++-------- 3 files changed, 61 insertions(+), 22 deletions(-) diff --git a/Usuarios/Controladores/consultarListaUsuarios.controller.js b/Usuarios/Controladores/consultarListaUsuarios.controller.js index 1a52225f..70e675df 100644 --- a/Usuarios/Controladores/consultarListaUsuarios.controller.js +++ b/Usuarios/Controladores/consultarListaUsuarios.controller.js @@ -26,9 +26,20 @@ exports.consultarListaUsuarios = async (req, res) => { .json({ mensaje: MENSAJES_USUARIOS.USUARIOS_NO_ENCONTRADOS.mensaje }); } + const generoMap = { + masculino: 'Hombre', + femenino: 'Mujer', + otro: 'Otro', + }; + + const resultadosMapeados = resultados.map((usuario) => ({ + ...usuario, + genero: generoMap[usuario.genero] || usuario.genero, + })); + return res.status(MENSAJES_USUARIOS.LISTA_USUARIOS_OBTENIDA.codigo).json({ mensaje: MENSAJES_USUARIOS.LISTA_USUARIOS_OBTENIDA.mensaje, - listaUsuarios: resultados, + listaUsuarios: resultadosMapeados, }); } catch { return res diff --git a/Usuarios/Datos/Repositorios/repositorioActualizarUsuario.js b/Usuarios/Datos/Repositorios/repositorioActualizarUsuario.js index c9b0d69a..3cb1f688 100644 --- a/Usuarios/Datos/Repositorios/repositorioActualizarUsuario.js +++ b/Usuarios/Datos/Repositorios/repositorioActualizarUsuario.js @@ -26,13 +26,36 @@ exports.actualizarUsuario = async (datos) => { } try { await Promise.all( - datos.map(({ idUsuario, nombreCompleto, correoElectronico, telefono }) => { - return correrQuery(CONSULTAS_USUARIOS.ACTUALIZAR, [ + datos.map(async (usuario) => { + const { + idUsuario, nombreCompleto, correoElectronico, - telefono, + contrasenia, + numeroTelefono, + direccion, + fechaNacimiento, + genero, + estatus, + idCliente, + } = usuario; + + // Actualiza datos del usuario + await correrQuery(CONSULTAS_USUARIOS.ACTUALIZAR_DATOS_USUARIO, [ + nombreCompleto, + correoElectronico, + contrasenia, + numeroTelefono, + direccion, + fechaNacimiento, + genero, + estatus, idUsuario, ]); + + if (idCliente !== undefined) { + await correrQuery(CONSULTAS_USUARIOS.ACTUALIZAR_CLIENTE_USUARIO, [idCliente, idUsuario]); + } }) ); } catch { diff --git a/Utilidades/Constantes/consultasUsuarios.js b/Utilidades/Constantes/consultasUsuarios.js index 919d8122..c7589a64 100644 --- a/Utilidades/Constantes/consultasUsuarios.js +++ b/Utilidades/Constantes/consultasUsuarios.js @@ -54,7 +54,7 @@ module.exports = { u.fechaNacimiento, u.genero, u.estatus, - r.nombre AS rol, + r.idRol AS rol, uc.idCliente, c.nombreComercial AS nombreCliente FROM usuario u @@ -66,23 +66,22 @@ module.exports = { `, OBTENER_LISTA: ` - SELECT u.idUsuario, - u.nombreCompleto AS nombre, - r.nombre AS rol, - c.nombreComercial AS cliente, - u.estatus, - u.correoElectronico AS correo, - u.numeroTelefono AS telefono - FROM usuario u - LEFT JOIN usuario_rol ur ON u.idUsuario = ur.idUsuario - LEFT JOIN rol r ON ur.idRol = r.idRol - LEFT JOIN usuario_cliente uc ON u.idUsuario = uc.idUsuario - LEFT JOIN cliente c ON uc.idCliente = c.idCliente - WHERE u.idUsuario NOT IN (SELECT ur2.idUsuario - FROM usuario_rol ur2 - WHERE ur2.idRol = 3); - - `, + SELECT u.idUsuario, + u.nombreCompleto AS nombre, + r.idRol AS rol, + c.nombreComercial AS cliente, + u.estatus, + u.correoElectronico AS correo, + u.numeroTelefono AS telefono + FROM usuario u + LEFT JOIN usuario_rol ur ON u.idUsuario = ur.idUsuario + LEFT JOIN rol r ON ur.idRol = r.idRol + LEFT JOIN usuario_cliente uc ON u.idUsuario = uc.idUsuario + LEFT JOIN cliente c ON uc.idCliente = c.idCliente + WHERE u.idUsuario NOT IN (SELECT ur2.idUsuario + FROM usuario_rol ur2 + WHERE ur2.idRol = 3); +`, ELIMINAR_USUARIOS: ` DELETE @@ -103,6 +102,12 @@ UPDATE usuario SET WHERE idUsuario = ?; `, + ACTUALIZAR_CLIENTE_USUARIO: ` +UPDATE usuario_cliente SET + idCliente = ? +WHERE idUsuario = ?; + `, + ACTUALIZAR_ROL_USUARIO: ` UPDATE usuario_rol SET idRol = ? From bf568101903d576495a339dbe2dfe5a889113fc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Antonio=20Ben=C3=ADtez=20De=20La=20Portilla?= Date: Tue, 3 Jun 2025 15:25:39 -0600 Subject: [PATCH 057/116] refactor: renombrar varibales en Camel case --- .../Datos/Repositorios/leerSetCuotasRepositorio.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Cuotas/Datos/Repositorios/leerSetCuotasRepositorio.js b/Cuotas/Datos/Repositorios/leerSetCuotasRepositorio.js index 62a4c649..e5831468 100644 --- a/Cuotas/Datos/Repositorios/leerSetCuotasRepositorio.js +++ b/Cuotas/Datos/Repositorios/leerSetCuotasRepositorio.js @@ -12,16 +12,16 @@ const CONSULTAS_CUOTAS = require('@altertex/util/const/consultasCuotas'); */ exports.obtenerSetCuotaPorId = async (idSetCuota) => { const query = CONSULTAS_CUOTAS.LEER_CUOTA_SET; - const query_cuotas = CONSULTAS_CUOTAS.LEER_CUOTA_SET_PRODUCTOS; + const queryCuotas = CONSULTAS_CUOTAS.LEER_CUOTA_SET_PRODUCTOS; const resultado = await correrQuery(query, [idSetCuota]); if (resultado.length === 0) return null; - const productos_cuota = await correrQuery(query_cuotas, [idSetCuota]); - const productos = productos_cuota.map((producto) => ({ + const productosCuota = await correrQuery(queryCuotas, [idSetCuota]); + const productos = productosCuota.map((producto) => ({ nombre: producto.nombreComun, })); - const cuotas = productos_cuota.map((producto) => ({ + const cuotas = productosCuota.map((producto) => ({ valor: producto.cuota_valor, })); @@ -29,8 +29,8 @@ exports.obtenerSetCuotaPorId = async (idSetCuota) => { idSetCuota: resultado[0].idCuotaSet, nombre: resultado[0].nombre, descripcion: resultado[0].descripcion, - productos: productos, - cuotas: cuotas, + productos, + cuotas, }; return setCuota; From 836b786463018c39df301594e4b7507a33d0de1b Mon Sep 17 00:00:00 2001 From: max Date: Wed, 4 Jun 2025 12:12:35 -0600 Subject: [PATCH 058/116] feat(usuarios): enhance user-client association logic in actualizarUsuario function --- .../Datos/Repositorios/repositorioActualizarUsuario.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Usuarios/Datos/Repositorios/repositorioActualizarUsuario.js b/Usuarios/Datos/Repositorios/repositorioActualizarUsuario.js index 3cb1f688..9cd79266 100644 --- a/Usuarios/Datos/Repositorios/repositorioActualizarUsuario.js +++ b/Usuarios/Datos/Repositorios/repositorioActualizarUsuario.js @@ -54,7 +54,15 @@ exports.actualizarUsuario = async (datos) => { ]); if (idCliente !== undefined) { - await correrQuery(CONSULTAS_USUARIOS.ACTUALIZAR_CLIENTE_USUARIO, [idCliente, idUsuario]); + // Intenta actualizar primero + const resultado = await correrQuery(CONSULTAS_USUARIOS.ACTUALIZAR_CLIENTE_USUARIO, [ + idCliente, + idUsuario, + ]); + // Si no se actualizó ninguna fila, inserta la relación + if (resultado.affectedRows === 0) { + await correrQuery(CONSULTAS_USUARIOS.ASOCIAR_USUARIO_A_CLIENTE, [idUsuario, idCliente]); + } } }) ); From 868ba29f4c9bde065c4481c7a5cc61e9f74f80de Mon Sep 17 00:00:00 2001 From: Diego Alfaro Pinto Date: Wed, 4 Jun 2025 13:43:50 -0600 Subject: [PATCH 059/116] feat: agregar funcionalidad para editar un rol --- .../Controladores/actualizarRol.controller.js | 34 +++++++ .../Repositorios/repositorioActualizarRol.js | 95 +++++++++++++++++++ .../RutasIndividuales/actualizarRol.routes.js | 15 +++ Roles/Rutas/indexRoles.routes.js | 4 + Utilidades/Constantes/consultasRoles.js | 6 ++ Utilidades/Constantes/mensajesRoles.js | 6 +- Utilidades/Constantes/rutas.js | 3 +- 7 files changed, 161 insertions(+), 2 deletions(-) create mode 100644 Roles/Controladores/actualizarRol.controller.js create mode 100644 Roles/Datos/Repositorios/repositorioActualizarRol.js create mode 100644 Roles/Rutas/RutasIndividuales/actualizarRol.routes.js diff --git a/Roles/Controladores/actualizarRol.controller.js b/Roles/Controladores/actualizarRol.controller.js new file mode 100644 index 00000000..6cca3400 --- /dev/null +++ b/Roles/Controladores/actualizarRol.controller.js @@ -0,0 +1,34 @@ +const MENSAJES = require('@altertex/util/const/mensajesRoles'); +const repositorio = require('@altertex/rol/repos/repositorioActualizarRol'); + +/** + * Controlador para actualizar un rol. + * + * Este controlador recibe los datos del rol a actualizar desde el cuerpo de la solicitud + * y el cliente seleccionado desde el usuario autenticado. Valida los datos y delega + * la lógica de actualización al repositorio. + * + * @function + * @param {Express.Request} req - Objeto de solicitud de Express. + * @param {Express.Response} res - Objeto de respuesta de Express. + * @returns {Promise} - Retorna una respuesta HTTP con el resultado de la operación. + */ +exports.actualizarRol = async (req, res) => { + const idCliente = req.user.clienteSeleccionado; + const datosActualizacion = req.body.datosRolActualizacion; + + if (!idCliente) { + return res.status(400).json({ mensaje: 'No se ha seleccionado un cliente.' }); + } + + if (!datosActualizacion) { + return res.status(MENSAJES.PARAMETROS_INVALIDOS.codigo).json({ mensaje: MENSAJES.PARAMETROS_INVALIDOS.mensaje }); + } + + try { + await repositorio.actualizarRol(idCliente, datosActualizacion); + return res.status(MENSAJES.ACTUALIZAR_ROL.codigo).json({ mensaje: MENSAJES.ACTUALIZAR_ROL.mensaje }); + } catch (error) { + return res.status(400).json({ mensaje: error.message }); + } +}; \ No newline at end of file diff --git a/Roles/Datos/Repositorios/repositorioActualizarRol.js b/Roles/Datos/Repositorios/repositorioActualizarRol.js new file mode 100644 index 00000000..ab942861 --- /dev/null +++ b/Roles/Datos/Repositorios/repositorioActualizarRol.js @@ -0,0 +1,95 @@ +const MENSAJES = require('@altertex/util/const/mensajesRoles'); +const conexion = require('@altertex/util/bd/db'); + +/** + * Actualiza un rol existente, incluyendo su nombre, descripción y permisos asociados. + * + * @param {number} idCliente - ID del cliente que realiza la actualización. + * @param {object} datosActualizarRol - Datos necesarios para actualizar el rol. + * @param {object} datosActualizarRol.datosRol - Contiene el nombre, descripción y permisos del rol. + * @param {number} datosActualizarRol.idRol - ID del rol a actualizar. + * @throws {Error} Si hay parámetros inválidos o errores en la base de datos. + */ +exports.actualizarRol = async (idCliente, datosActualizarRol) => { + const datosRol = datosActualizarRol.datosRol; + const idRol = datosActualizarRol.idRol; + const permisos = datosRol.permisos || []; + + if (!idCliente || !datosActualizarRol || !idRol) { + throw new Error(MENSAJES.PARAMETROS_INVALIDOS.mensaje); + } + + const conexionBD = await conexion.getConnection(); + + try { + await conexionBD.beginTransaction(); + + // Check for duplicate name only if name is being updated + if (datosRol.nombre !== null && datosRol.nombre !== undefined) { + const resultadoNombreDuplicado = await conexionBD.query( + 'SELECT idRol FROM rol WHERE nombre = ? AND idRol != ?', + [datosRol.nombre, idRol], + ); + + if (resultadoNombreDuplicado[0].length > 0) { + throw new Error(MENSAJES.ROL_EXISTENTE); + } + } + + // Build dynamic UPDATE query based on which fields are provided + const camposActualizar = []; + const valoresActualizar = []; + + if (datosRol.nombre !== null && datosRol.nombre !== undefined) { + camposActualizar.push('nombre = ?'); + valoresActualizar.push(datosRol.nombre); + } + + if (datosRol.descripcion !== null && datosRol.descripcion !== undefined) { + camposActualizar.push('descripcion = ?'); + valoresActualizar.push(datosRol.descripcion); + } + + // Only run UPDATE if there are fields to update + if (camposActualizar.length > 0) { + const consultaActualizar = `UPDATE rol + SET ${camposActualizar.join(', ')} + WHERE idRol = ?`; + valoresActualizar.push(idRol); + + await conexionBD.query(consultaActualizar, valoresActualizar); + } + + // Always handle permissions (delete old ones) + await conexionBD.query( + 'DELETE FROM rol_permiso WHERE idRol = ?', + [idRol], + ); + + // Insert new permissions if any + if (permisos.length > 0) { + const valores = permisos.map(idPermiso => [idRol, idPermiso]); + const marcadores = permisos.map(() => '(?, ?)').join(', '); + const consultaInsertar = `INSERT INTO rol_permiso (idRol, idPermiso) + VALUES ${marcadores}`; + const valoresAplanados = valores.flat(); + + await conexionBD.query(consultaInsertar, valoresAplanados); + } + + await conexionBD.commit(); + + } catch (error) { + await conexionBD.rollback(); + console.error('Error actualizando rol:', error); + + if (error.message === MENSAJES.ROL_EXISTENTE + || error.message === MENSAJES.PARAMETROS_INVALIDOS.mensaje) { + throw new Error(error.message); + } else { + throw new Error('Ocurrio un error al actualizar rol.'); + } + } finally { + conexionBD.release(); + } +}; \ No newline at end of file diff --git a/Roles/Rutas/RutasIndividuales/actualizarRol.routes.js b/Roles/Rutas/RutasIndividuales/actualizarRol.routes.js new file mode 100644 index 00000000..76fe2262 --- /dev/null +++ b/Roles/Rutas/RutasIndividuales/actualizarRol.routes.js @@ -0,0 +1,15 @@ +const express = require('express'); +const ruteador = express.Router(); +const controlador = require('@altertex/rol/ctrl/actualizarRol.controller'); + +const PERMISOS = require('@altertex/util/const/permisos'); +const RUTAS = require('@altertex/util/const/rutas'); +const validarYSanitizar = require('@altertex/util/inter/validarYSanitizar'); +const revisarApiKey = require('@altertex/util/inter/revisarApiKey'); +const autorizarToken = require('@altertex/util/inter/autorizarToken'); +const limitePeticionesDiarias = require('@altertex/util/inter/limitePeticiones'); +const revisarPermisos = require('@altertex/util/inter/verificarPermisos'); + +ruteador.put(RUTAS.ROLES.ACTUALIZAR, validarYSanitizar, revisarApiKey(), autorizarToken, limitePeticionesDiarias, revisarPermisos(PERMISOS.ACTUALIZAR_ROL), controlador.actualizarRol); + +module.exports = ruteador; \ No newline at end of file diff --git a/Roles/Rutas/indexRoles.routes.js b/Roles/Rutas/indexRoles.routes.js index 1fa63569..adadbca0 100644 --- a/Roles/Rutas/indexRoles.routes.js +++ b/Roles/Rutas/indexRoles.routes.js @@ -23,6 +23,8 @@ const rutasEliminarRol = require('@altertex/rol/rutasInd/eliminarRol.routes'); const rutasConsultarDetalle = require('@altertex/rol/rutasInd/consultarDetalleRol.routes'); +const rutasActualizarRol = require('@altertex/rol/rutasInd/actualizarRol.routes'); + // Importación del archivo de constantes donde están definidas las rutas base del sistema. const RUTAS = require('@altertex/util/const/rutas'); @@ -43,5 +45,7 @@ ruteador.use(RUTAS.ROLES.BASE, rutasEliminarRol); ruteador.use(RUTAS.ROLES.BASE, rutasConsultarDetalle); +ruteador.use(RUTAS.ROLES.BASE, rutasActualizarRol); + // Exporta el enrutador para ser utilizado en el archivo principal de rutas de la aplicación (por ejemplo: app.js). module.exports = ruteador; \ No newline at end of file diff --git a/Utilidades/Constantes/consultasRoles.js b/Utilidades/Constantes/consultasRoles.js index d4f72f9c..3f6de048 100644 --- a/Utilidades/Constantes/consultasRoles.js +++ b/Utilidades/Constantes/consultasRoles.js @@ -82,4 +82,10 @@ module.exports = { WHERE r.idRol = ?; `, + VERIFICAR_NOMBRE_DUPLICADO_ROL: ` + select * + from rol + where nombre = ?; + `, + }; \ No newline at end of file diff --git a/Utilidades/Constantes/mensajesRoles.js b/Utilidades/Constantes/mensajesRoles.js index 3eef144f..d8ab3f05 100644 --- a/Utilidades/Constantes/mensajesRoles.js +++ b/Utilidades/Constantes/mensajesRoles.js @@ -76,9 +76,13 @@ module.exports = { codigo: 400, mensaje: 'Ocurrió un error al eliminar rol', mensaje_no_existe: 'Ocurrió un error, rol no existe', - mensaje_rol_asignado: 'No se puede eliminar el rol porque está asignado a uno o más usuarios.' + mensaje_rol_asignado: 'No se puede eliminar el rol porque está asignado a uno o más usuarios.', }, + ACTUALIZAR_ROL: { + codigo: 200, + mensaje: 'Se actualizo correctamente el rol.', + }, NOMBRE_OBLIGATORIO: 'El nombre del rol es obligatorio.', PERMISOS_OBLIGATORIOS: 'Debes seleccionar al menos un permiso.', diff --git a/Utilidades/Constantes/rutas.js b/Utilidades/Constantes/rutas.js index f3ef0616..d33adc20 100644 --- a/Utilidades/Constantes/rutas.js +++ b/Utilidades/Constantes/rutas.js @@ -40,7 +40,7 @@ module.exports = { CONSULTAR_LISTA: '/consultar-lista', CREAR: '/crear', ELIMINAR_PRODUCTO: '/eliminar', - LEER: '/leer-producto' + LEER: '/leer-producto', }, PROVEEDORES: { BASE: '/proveedores', @@ -89,6 +89,7 @@ module.exports = { CONFIRMAR_CREACION: '/confirmar-creacion', ELIMINAR_ROL: '/eliminar', LEER_ROL: '/leer', + ACTUALIZAR: '/actualizar-rol', }, PEDIDOS: { BASE: '/pedidos', From 3164645ff25c661f220f678a11f57636892d0b18 Mon Sep 17 00:00:00 2001 From: NicoH00d Date: Wed, 4 Jun 2025 23:28:34 -0600 Subject: [PATCH 060/116] feat: agregar generacion de SKU Automatico --- .../importarProductos.controller.js | 20 ++++-- .../Validaciones/validarOpcionesImportar.js | 8 --- Utilidades/Intermediarios/generarSKUAuto.js | 67 +++++++++++++++++++ 3 files changed, 83 insertions(+), 12 deletions(-) create mode 100644 Utilidades/Intermediarios/generarSKUAuto.js diff --git a/Productos/Controladores/importarProductos.controller.js b/Productos/Controladores/importarProductos.controller.js index ffb79f02..45c861c2 100644 --- a/Productos/Controladores/importarProductos.controller.js +++ b/Productos/Controladores/importarProductos.controller.js @@ -5,6 +5,7 @@ const repositorioCrearVariante = require('@altertex/pro/repos/repositorioCrearVa const repositorioCrearOpcion = require('@altertex/pro/repos/repositorioCrearOpcion'); const db = require('@altertex/util/bd/db'); const validarProductoImportado = require('@altertex/util/vali/validarProductoImportado'); +const { crearGeneradorSKUConsecutivo } = require('@altertex/util/inter/generarSKUAuto'); /** * Importa productos y sus variantes/opciones para un cliente. @@ -85,15 +86,26 @@ exports.importarProductos = async (req, res) => { errores, }); } - + const generarSKUConsecutivo = crearGeneradorSKUConsecutivo(); for (let im = 0; im < productos.length; im += 1) { const { producto, variantes } = productos[im]; const idProducto = await repositorioCrearProducto.crearProducto(idCliente, producto); - for (const variante of variantes) { + for (const variante of variantes) { const idVariante = await repositorioCrearVariante.crearVariante(idProducto, variante); - await repositorioCrearOpcion.crearOpcion(idVariante, variante.opciones); - } + + const opcionesConSKU = variante.opciones.map(opcion => ({ + ...opcion, + SKUautomatico: generarSKUConsecutivo( + producto.nombreComun, + variante.nombreVariante, + opcion.valorOpcion || 'SINVALOR' + ) + })); + + console.log('🚨 OPCIONES CON SKU', opcionesConSKU); + await repositorioCrearOpcion.crearOpcion(idVariante, opcionesConSKU); + } } await conexion.commit(); diff --git a/Utilidades/Intermediarios/Validaciones/validarOpcionesImportar.js b/Utilidades/Intermediarios/Validaciones/validarOpcionesImportar.js index 6be7ce7e..69e7775e 100644 --- a/Utilidades/Intermediarios/Validaciones/validarOpcionesImportar.js +++ b/Utilidades/Intermediarios/Validaciones/validarOpcionesImportar.js @@ -55,14 +55,6 @@ module.exports = (opciones) => { }; } - // prettier-ignore - if (!opcion.SKUautomatico - || opcion.SKUautomatico == null - || typeof opcion.SKUautomatico !== 'string' - || opcion.SKUautomatico.length > 50 - ) { - return { error: 'SKUautomatico es requerido y debe ser una cadena de texto de máximo 50 caracteres.' }; - } // prettier-ignore if (!opcion.SKUcomercial || opcion.SKUcomercial == null diff --git a/Utilidades/Intermediarios/generarSKUAuto.js b/Utilidades/Intermediarios/generarSKUAuto.js new file mode 100644 index 00000000..11262a7c --- /dev/null +++ b/Utilidades/Intermediarios/generarSKUAuto.js @@ -0,0 +1,67 @@ +/** + * Limpia el texto eliminando acentos, caracteres especiales y lo convierte a mayúsculas. + * @param {string} texto - El texto a limpiar. + * @returns {string} El texto limpio. + */ +const limpiarTexto = (texto) => + (typeof texto === 'string' ? texto : '') + .normalize('NFD') + .replace(/[\u0300-\u036f]/g, '') + .replace(/[^a-zA-Z0-9\s]/g, '') + .toUpperCase(); + +/** + * Obtiene un código basado en el texto proporcionado, usando la primera palabra que cumpla la longitud mínima. + * @param {string} texto - El texto del que se extraerá el código. + * @param {number} [longitud=3] - La longitud mínima del código a extraer. + * @returns {string} El código generado a partir del texto. + */ +const obtenerCodigo = (texto, longitud = 3) => { + const palabras = limpiarTexto(texto).split(' '); + for (const palabra of palabras) { + if (palabra.length >= longitud) return palabra.substring(0, longitud); + } + return limpiarTexto(texto).substring(0, longitud); +}; + + +/** + * Genera un SKU basado en el nombre del producto, variante y valor de opción. + * @param {string} nombreProducto - El nombre del producto. + * @param {string} nombreVariante - El nombre de la variante. + * @param {string} valorOpcion - El valor de la opción. + * @returns {string} El SKU generado. + */ +const generarSKU = (nombreProducto, nombreVariante, valorOpcion) => { + try { + const prefijo = obtenerCodigo(nombreProducto); + const codigoVariante = obtenerCodigo(nombreVariante); + const codigoOpcion = obtenerCodigo(valorOpcion); + return `${prefijo}-${codigoVariante}-${codigoOpcion}`; + } catch { + console.error('Error generando SKU:', { nombreProducto, nombreVariante, valorOpcion }); + return 'SKU-ERROR'; + } +}; + +/** + * Crea una función generadora de SKUs consecutivos basada en los parámetros dados. + * @returns {function(string, string, string): string} Función que genera un SKU único e incremental. + */ +const crearGeneradorSKUConsecutivo = () => { + const contadorSKU = new Map(); + + return (nombreProducto, nombreVariante, valorOpcion) => { + const base = generarSKU(nombreProducto, nombreVariante, valorOpcion); + const actual = contadorSKU.get(base) || 0; + const siguiente = actual + 1; + contadorSKU.set(base, siguiente); + + return `${base}-${String(siguiente).padStart(3, '0')}`; + }; +}; + +module.exports = { + generarSKU, // solo base + crearGeneradorSKUConsecutivo // base + numeración incremental +}; From 782fd400a5a8fe8fb2cea459efbfc9131bedefe9 Mon Sep 17 00:00:00 2001 From: NicoH00d Date: Wed, 4 Jun 2025 23:29:41 -0600 Subject: [PATCH 061/116] fix: eliminar comentarios de debugging --- Productos/Controladores/importarProductos.controller.js | 1 - 1 file changed, 1 deletion(-) diff --git a/Productos/Controladores/importarProductos.controller.js b/Productos/Controladores/importarProductos.controller.js index 45c861c2..3fac5842 100644 --- a/Productos/Controladores/importarProductos.controller.js +++ b/Productos/Controladores/importarProductos.controller.js @@ -103,7 +103,6 @@ exports.importarProductos = async (req, res) => { ) })); - console.log('🚨 OPCIONES CON SKU', opcionesConSKU); await repositorioCrearOpcion.crearOpcion(idVariante, opcionesConSKU); } } From 535aadaaff7f958f624de2eb935d4132f65f68df Mon Sep 17 00:00:00 2001 From: Diego Alfaro Pinto Date: Thu, 5 Jun 2025 10:11:17 -0600 Subject: [PATCH 062/116] fix: quitar parametro innecesario --- .idea/sqldialects.xml | 12 ------------ Roles/Controladores/actualizarRol.controller.js | 6 +----- Roles/Datos/Repositorios/repositorioActualizarRol.js | 5 ++--- 3 files changed, 3 insertions(+), 20 deletions(-) delete mode 100644 .idea/sqldialects.xml diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml deleted file mode 100644 index bb491146..00000000 --- a/.idea/sqldialects.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/Roles/Controladores/actualizarRol.controller.js b/Roles/Controladores/actualizarRol.controller.js index 6cca3400..a9d7cac3 100644 --- a/Roles/Controladores/actualizarRol.controller.js +++ b/Roles/Controladores/actualizarRol.controller.js @@ -14,19 +14,15 @@ const repositorio = require('@altertex/rol/repos/repositorioActualizarRol'); * @returns {Promise} - Retorna una respuesta HTTP con el resultado de la operación. */ exports.actualizarRol = async (req, res) => { - const idCliente = req.user.clienteSeleccionado; const datosActualizacion = req.body.datosRolActualizacion; - if (!idCliente) { - return res.status(400).json({ mensaje: 'No se ha seleccionado un cliente.' }); - } if (!datosActualizacion) { return res.status(MENSAJES.PARAMETROS_INVALIDOS.codigo).json({ mensaje: MENSAJES.PARAMETROS_INVALIDOS.mensaje }); } try { - await repositorio.actualizarRol(idCliente, datosActualizacion); + await repositorio.actualizarRol(datosActualizacion); return res.status(MENSAJES.ACTUALIZAR_ROL.codigo).json({ mensaje: MENSAJES.ACTUALIZAR_ROL.mensaje }); } catch (error) { return res.status(400).json({ mensaje: error.message }); diff --git a/Roles/Datos/Repositorios/repositorioActualizarRol.js b/Roles/Datos/Repositorios/repositorioActualizarRol.js index ab942861..22e67561 100644 --- a/Roles/Datos/Repositorios/repositorioActualizarRol.js +++ b/Roles/Datos/Repositorios/repositorioActualizarRol.js @@ -4,18 +4,17 @@ const conexion = require('@altertex/util/bd/db'); /** * Actualiza un rol existente, incluyendo su nombre, descripción y permisos asociados. * - * @param {number} idCliente - ID del cliente que realiza la actualización. * @param {object} datosActualizarRol - Datos necesarios para actualizar el rol. * @param {object} datosActualizarRol.datosRol - Contiene el nombre, descripción y permisos del rol. * @param {number} datosActualizarRol.idRol - ID del rol a actualizar. * @throws {Error} Si hay parámetros inválidos o errores en la base de datos. */ -exports.actualizarRol = async (idCliente, datosActualizarRol) => { +exports.actualizarRol = async (datosActualizarRol) => { const datosRol = datosActualizarRol.datosRol; const idRol = datosActualizarRol.idRol; const permisos = datosRol.permisos || []; - if (!idCliente || !datosActualizarRol || !idRol) { + if (!datosActualizarRol || !idRol) { throw new Error(MENSAJES.PARAMETROS_INVALIDOS.mensaje); } From b4ec2cd2d98b0bf16056e7ec33480cade64a0530 Mon Sep 17 00:00:00 2001 From: PAOLA MARIA garrido Date: Thu, 5 Jun 2025 10:22:51 -0600 Subject: [PATCH 063/116] fix:corregir mensaje de error --- .../consultarListaCategorias.routes.js | 2 +- .../RutasIndividuales/eliminarCliente.routes.js | 4 +--- .../RutasIndividuales/leerCliente.routes.js | 2 +- .../exportarEmpleados.routes.js | 2 +- .../RutasIndividuales/leerUsuario.routes.js | 2 +- Utilidades/Constantes/mensajesCategorias.js | 8 ++++---- Utilidades/Constantes/mensajesClientes.js | 17 +++++++++-------- Utilidades/Constantes/mensajesEmpleados.js | 8 ++++---- Utilidades/Constantes/mensajesUsuarios.js | 8 ++++---- 9 files changed, 26 insertions(+), 27 deletions(-) diff --git a/Categorias/Rutas/RutasIndividuales/consultarListaCategorias.routes.js b/Categorias/Rutas/RutasIndividuales/consultarListaCategorias.routes.js index f9e482e1..e5b99c36 100644 --- a/Categorias/Rutas/RutasIndividuales/consultarListaCategorias.routes.js +++ b/Categorias/Rutas/RutasIndividuales/consultarListaCategorias.routes.js @@ -61,7 +61,7 @@ * mensaje: * type: string * example: "No se encontraron categorías registradas." - * 500: + * 400: * description: Error en el servidor al intentar obtener la lista de categorías. * content: * application/json: diff --git a/Clientes/Rutas/RutasIndividuales/eliminarCliente.routes.js b/Clientes/Rutas/RutasIndividuales/eliminarCliente.routes.js index 0e2f26cf..bd0e0b0d 100644 --- a/Clientes/Rutas/RutasIndividuales/eliminarCliente.routes.js +++ b/Clientes/Rutas/RutasIndividuales/eliminarCliente.routes.js @@ -28,11 +28,9 @@ * mensaje: * type: string * example: Cliente eliminado - * 400: - * description: No se puede eliminar el cliente debido a restricciones (ej. registros asociados, ID inválido). * 404: * description: No se encontró un cliente con el ID proporcionado. - * 500: + * 400: * description: Error interno al eliminar el cliente */ diff --git a/Clientes/Rutas/RutasIndividuales/leerCliente.routes.js b/Clientes/Rutas/RutasIndividuales/leerCliente.routes.js index d5430bb1..803bae07 100644 --- a/Clientes/Rutas/RutasIndividuales/leerCliente.routes.js +++ b/Clientes/Rutas/RutasIndividuales/leerCliente.routes.js @@ -66,7 +66,7 @@ * mensaje: * type: string * example: "No se encontró un cliente con el ID proporcionado." - * 500: + * 400: * description: Error interno del servidor al consultar el cliente. * content: * application/json: diff --git a/Empleados/Rutas/RutasIndividuales/exportarEmpleados.routes.js b/Empleados/Rutas/RutasIndividuales/exportarEmpleados.routes.js index 2d4d0b8a..a51ccfe8 100644 --- a/Empleados/Rutas/RutasIndividuales/exportarEmpleados.routes.js +++ b/Empleados/Rutas/RutasIndividuales/exportarEmpleados.routes.js @@ -41,7 +41,7 @@ * mensaje: * type: string * example: No hay empleados para exportar. - * 500: + * 400: * description: Error interno del servidor al exportar empleados. * content: * application/json: diff --git a/Usuarios/Rutas/RutasIndividuales/leerUsuario.routes.js b/Usuarios/Rutas/RutasIndividuales/leerUsuario.routes.js index 084d5f62..4485e114 100644 --- a/Usuarios/Rutas/RutasIndividuales/leerUsuario.routes.js +++ b/Usuarios/Rutas/RutasIndividuales/leerUsuario.routes.js @@ -85,7 +85,7 @@ * mensaje: * type: string * example: "No se encontró un usuario con el ID proporcionado." - * 500: + * 400: * description: Error interno del servidor al consultar el usuario. * content: * application/json: diff --git a/Utilidades/Constantes/mensajesCategorias.js b/Utilidades/Constantes/mensajesCategorias.js index 4b860b8e..e42a9df4 100644 --- a/Utilidades/Constantes/mensajesCategorias.js +++ b/Utilidades/Constantes/mensajesCategorias.js @@ -42,6 +42,10 @@ module.exports = { codigo: 400, mensaje: 'Los parámetros proporcionados no son válidos.', }, + ERROR_OBTENER_CATEGORIAS: { + codigo: 400, + mensaje: 'Ocurrió un error al obtener la lista de categorías.', + }, // 401 - No autorizado CREDENCIALES_INVALIDAS: { @@ -66,10 +70,6 @@ module.exports = { codigo: 400, mensaje: 'Ocurrió un error al intentar crear la categoría.', }, - ERROR_OBTENER_CATEGORIAS: { - codigo: 400, - mensaje: 'Ocurrió un error al obtener la lista de categorías.', - }, ERROR_OBTENER_CATEGORIA: { codigo: 400, mensaje: 'Ocurrió un error al obtener los datos de la categoría.', diff --git a/Utilidades/Constantes/mensajesClientes.js b/Utilidades/Constantes/mensajesClientes.js index 1d4889f2..3e5ea4e5 100644 --- a/Utilidades/Constantes/mensajesClientes.js +++ b/Utilidades/Constantes/mensajesClientes.js @@ -44,6 +44,14 @@ module.exports = { codigo: 400, mensaje: 'El ID del cliente debe ser un número entero válido.', }, + ERROR_ELIMINAR_CLIENTE: { + codigo: 400, + mensaje: 'Ocurrió un error al eliminar el cliente.', + }, + ERROR_CONSULTAR_CLIENTE: { + codigo: 400, + mensaje: 'Ocurrió un error al obtener la información del cliente.', + }, // 403 - Forbidden ACCESO_NO_AUTORIZADO: { @@ -62,10 +70,6 @@ module.exports = { }, // 500 - Internal Server Error - ERROR_CONSULTAR_CLIENTE: { - codigo: 500, - mensaje: 'Ocurrió un error al obtener la información del cliente.', - }, ERROR_CONSULTAR_SISTEMA: { codigo: 500, mensaje: 'Ocurrió un error al obtener la información del sistema del cliente.', @@ -74,10 +78,7 @@ module.exports = { codigo: 500, mensaje: 'Ocurrió un error al obtener la lista de clientes.', }, - ERROR_ELIMINAR_CLIENTE: { - codigo: 500, - mensaje: 'Ocurrió un error al eliminar el cliente.', - }, + // Crear cliente CAMPO_OBLIGATORIO: { diff --git a/Utilidades/Constantes/mensajesEmpleados.js b/Utilidades/Constantes/mensajesEmpleados.js index c0cdd18a..b8f20462 100644 --- a/Utilidades/Constantes/mensajesEmpleados.js +++ b/Utilidades/Constantes/mensajesEmpleados.js @@ -28,6 +28,10 @@ module.exports = { codigo: 400, mensaje: 'Error al actualizar', }, + ERROR_EXPORTAR_EMPLEADOS: { + codigo: 400, + mensaje: 'Error al exportar la lista de empleados.' + }, // 403 - Forbidden PERMISO_DENEGADO: { @@ -84,9 +88,5 @@ module.exports = { GRUPO_NOMBRE_REPETIDO: { codigo: 'GRUPO_NOMBRE_REPETIDO', mensaje: 'Ya existe un grupo con ese nombre.', - }, - ERROR_EXPORTAR_EMPLEADOS: { - codigo: 500, - mensaje: 'Error al exportar la lista de empleados.' } }; diff --git a/Utilidades/Constantes/mensajesUsuarios.js b/Utilidades/Constantes/mensajesUsuarios.js index ac2ddeee..898f47a2 100644 --- a/Utilidades/Constantes/mensajesUsuarios.js +++ b/Utilidades/Constantes/mensajesUsuarios.js @@ -51,6 +51,10 @@ module.exports = { codigo: 400, mensaje: 'Los parámetros proporcionados no son válidos.', }, + ERROR_OBTENER_USUARIO: { + codigo: 400, + mensaje: 'Ocurrió un error al obtener los datos del usuario.', + }, // 401 - sin autorizacion CREDENCIALES_INVALIDAS: { @@ -79,10 +83,6 @@ module.exports = { codigo: 500, mensaje: 'Ocurrió un error al obtener la lista de usuarios.', }, - ERROR_OBTENER_USUARIO: { - codigo: 500, - mensaje: 'Ocurrió un error al obtener los datos del usuario.', - }, ERROR_ELIMINAR_USUARIO: { codigo: 500, mensaje: 'Ocurrió un error al intentar eliminar el usuario.', From abb3c8eb356441d60aeef5bc0c693f96e0498c9f Mon Sep 17 00:00:00 2001 From: Hiram <147564077+Hiram10tec@users.noreply.github.com> Date: Thu, 5 Jun 2025 14:24:26 -0600 Subject: [PATCH 064/116] feature de actualizar categorias --- .../actualizarCategoria.controller.js | 31 ++++++++---- .../actualizarCategorias.routes.js | 49 +++++++++++++++++++ Utilidades/Constantes/consultasCategorias.js | 23 +++++---- Utilidades/Constantes/mensajesCategorias.js | 7 ++- 4 files changed, 87 insertions(+), 23 deletions(-) diff --git a/Categorias/Controladores/actualizarCategoria.controller.js b/Categorias/Controladores/actualizarCategoria.controller.js index 295391d7..8138e24d 100644 --- a/Categorias/Controladores/actualizarCategoria.controller.js +++ b/Categorias/Controladores/actualizarCategoria.controller.js @@ -4,8 +4,8 @@ const MENSAJES = require('@altertex/util/const/mensajesCategorias'); /** * RF49 - Actualizar categoría de productos - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF49 * - * @param {import('express').Request} req - * @param {import('express').Response} res + * @param {express.Request} req + * @param {express.Response} res * @returns {Promise} */ exports.actualizarCategoria = async (req, res) => { @@ -13,21 +13,32 @@ exports.actualizarCategoria = async (req, res) => { const { idCategoria } = req.params; const { nombreCategoria, descripcion, productos } = req.body; - if (!idCategoria || !nombreCategoria || typeof nombreCategoria !== 'string') { - return res.status(400).json(MENSAJES.PARAMETROS_INVALIDOS); + if (!idCategoria) { + return res.status(400).json(MENSAJES.CATEGORIA_NO_ENCONTRADA); } + if (!nombreCategoria || typeof nombreCategoria !== 'string' || nombreCategoria.trim() === '') { + return res.status(400).json(MENSAJES.NOMBRE_CATEGORIA_INVALIDO); + } + + if (!Array.isArray(productos)) { + return res.status(400).json({ + codigo: 400, + mensaje: 'El campo productos debe ser un arreglo.', + }); + } + + if (descripcion && typeof descripcion !== 'string') { + return res.status(400).json(MENSAJES.DESCRIPCION_INVALIDA); + } + await actualizarCategoria({ idCategoria, nombreCategoria, descripcion, productos }); return res.status(200).json({ codigo: 200, mensaje: 'Categoría actualizada correctamente.', }); - } catch (error) { - console.error('Error al actualizar categoría:', error); - return res.status(500).json({ - codigo: 500, - mensaje: 'Ocurrió un error al actualizar la categoría.', - }); + } catch { + return res.status(500).json(MENSAJES.ERROR_CREAR_CATEGORIA); } }; \ No newline at end of file diff --git a/Categorias/Rutas/RutasIndividuales/actualizarCategorias.routes.js b/Categorias/Rutas/RutasIndividuales/actualizarCategorias.routes.js index 34a74260..7f6a8df8 100644 --- a/Categorias/Rutas/RutasIndividuales/actualizarCategorias.routes.js +++ b/Categorias/Rutas/RutasIndividuales/actualizarCategorias.routes.js @@ -10,9 +10,58 @@ const autorizarToken = require('@altertex/util/inter/autorizarToken'); const verificarPermisos = require('@altertex/util/inter/verificarPermisos'); const PERMISOS = require('@altertex/util/const/permisos'); const RUTAS = require('@altertex/util/const/rutas'); +const validarYSanitizar = require('@altertex/util/inter/validarYSanitizar'); +/** + * @swagger + * /api/categorias/actualizar-categoria/{idCategoria}: + * put: + * summary: Actualiza una categoría y su lista de productos. + * description: Requiere autenticación y permisos adecuados. Valida que no se incluyan entradas maliciosas. + * tags: + * - Categorías + * security: + * - ApiKeyAuth: [] + * - BearerAuth: [] + * parameters: + * - in: path + * name: idCategoria + * required: true + * schema: + * type: integer + * description: ID de la categoría a actualizar + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * categoria: + * type: object + * properties: + * nombreCategoria: + * type: string + * descripcion: + * type: string + * productos: + * type: array + * items: + * type: object + * properties: + * idProducto: + * type: integer + * responses: + * 200: + * description: Categoría actualizada correctamente. + * 400: + * description: Datos inválidos o entrada maliciosa detectada. + * 500: + * description: Error interno al actualizar la categoría. + */ ruteador.put( `${RUTAS.CATEGORIAS.ACTUALIZAR}/:idCategoria`, + validarYSanitizar, revisarApiKey(), autorizarToken, verificarPermisos(PERMISOS.ACTUALIZAR_CATEGORIA_PRODUCTOS), diff --git a/Utilidades/Constantes/consultasCategorias.js b/Utilidades/Constantes/consultasCategorias.js index 23acdd34..9d9f34c9 100644 --- a/Utilidades/Constantes/consultasCategorias.js +++ b/Utilidades/Constantes/consultasCategorias.js @@ -1,17 +1,16 @@ module.exports = { OBTENER_CATEGORIAS_CON_PRODUCTOS: ` - SELECT c.idCategoria, - c.nombreCategoria, - c.descripcion, - COUNT(p.idProducto) AS cantidadProductos, - p.idCliente - FROM categoria c - JOIN - categoria_producto cp ON c.idCategoria = cp.idCategoria - JOIN - producto p ON cp.idProducto = p.idProducto - WHERE p.idCliente = ? - GROUP BY c.idCategoria, c.nombreCategoria, c.descripcion, p.idCliente; + SELECT + c.idCategoria, + c.nombreCategoria, + c.descripcion, + COUNT(p.idProducto) AS cantidadProductos + FROM + categoria c + LEFT JOIN categoria_producto cp ON c.idCategoria = cp.idCategoria + LEFT JOIN producto p ON cp.idProducto = p.idProducto AND p.idCliente = ? + GROUP BY + c.idCategoria, c.nombreCategoria, c.descripcion; `, CREAR_CATEGORIAS: ` diff --git a/Utilidades/Constantes/mensajesCategorias.js b/Utilidades/Constantes/mensajesCategorias.js index 4b860b8e..3635ee10 100644 --- a/Utilidades/Constantes/mensajesCategorias.js +++ b/Utilidades/Constantes/mensajesCategorias.js @@ -32,7 +32,7 @@ module.exports = { }, NOMBRE_CATEGORIA_INVALIDO: { codigo: 400, - mensaje: 'El nombre de la categoría proporcionado no es válido.', + mensaje: 'El nombre de la categoría es obligatorio.', }, CATEGORIA_YA_EXISTE: { codigo: 400, @@ -82,4 +82,9 @@ module.exports = { codigo: 400, mensaje: 'El producto no existe en la base de datos', }, + DESCRIPCION_INVALIDA: { + codigo: 400, + mensaje: 'La descripción proporcionada no es válida.', + }, + }; From 960f28d578d9f9f16a3606d3823585cd54709b3c Mon Sep 17 00:00:00 2001 From: PAOLA MARIA garrido Date: Thu, 5 Jun 2025 14:42:28 -0600 Subject: [PATCH 065/116] fix:correcion de lint y estatus --- Empleados/Controladores/exportarEmpleados.controller.js | 2 +- Empleados/Datos/Repositorios/repositorioExportarEmpleado.js | 1 - Utilidades/Constantes/consultasEmpleados.js | 6 +++++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Empleados/Controladores/exportarEmpleados.controller.js b/Empleados/Controladores/exportarEmpleados.controller.js index 8c7e61f3..f3d3e4c3 100644 --- a/Empleados/Controladores/exportarEmpleados.controller.js +++ b/Empleados/Controladores/exportarEmpleados.controller.js @@ -57,7 +57,7 @@ exports.exportarEmpleados = async (req, res) => { const parser = new Parser({ fields: campos }); const csv = parser.parse(empleados); - const csvConBOM = '\uFEFF' + csv; + const csvConBOM = `\uFEFF${csv}`; return res.status(MENSAJES_EMPLEADOS.LISTA_EMPLEADOS_EXPORTADA.codigo).json({ mensaje: MENSAJES_EMPLEADOS.LISTA_EMPLEADOS_EXPORTADA.mensaje, diff --git a/Empleados/Datos/Repositorios/repositorioExportarEmpleado.js b/Empleados/Datos/Repositorios/repositorioExportarEmpleado.js index 5e49be83..f8d7f19a 100644 --- a/Empleados/Datos/Repositorios/repositorioExportarEmpleado.js +++ b/Empleados/Datos/Repositorios/repositorioExportarEmpleado.js @@ -10,7 +10,6 @@ const CONSULTAS_EMPLEADOS = require('@altertex/util/const/consultasEmpleados'); * * @see [RF59 - Documentación de requisitos](https://codeandco-wiki.netlify.app/docs/next/proyectos/textiles/documentacion/requisitos/RF59) */ - exports.obtenerEmpleadosExportacion = (idCliente, idsEmpleado) => { const placeholders = idsEmpleado.map(() => '?').join(', '); const query = CONSULTAS_EMPLEADOS.OBTENER_DATOS_EXPORTACION.replace('__IDS__', placeholders); diff --git a/Utilidades/Constantes/consultasEmpleados.js b/Utilidades/Constantes/consultasEmpleados.js index 6648231f..b2503f7b 100644 --- a/Utilidades/Constantes/consultasEmpleados.js +++ b/Utilidades/Constantes/consultasEmpleados.js @@ -33,7 +33,11 @@ module.exports = { u.direccion, u.fechaNacimiento, u.genero, - u.estatus, + CASE + WHEN u.estatus = 1 THEN 'Activo' + WHEN u.estatus = 0 THEN 'Inactivo' + ELSE 'Desconocido' + END AS estatus, e.numeroEmergencia, e.areaTrabajo, e.posicion, From e66edd30ab358d37aacde7e1fdf684c924f1e26c Mon Sep 17 00:00:00 2001 From: Diego Alfaro Pinto Date: Thu, 5 Jun 2025 16:12:39 -0600 Subject: [PATCH 066/116] fix: arreglar errores --- Roles/Controladores/actualizarRol.controller.js | 1 - 1 file changed, 1 deletion(-) diff --git a/Roles/Controladores/actualizarRol.controller.js b/Roles/Controladores/actualizarRol.controller.js index a9d7cac3..0e129abb 100644 --- a/Roles/Controladores/actualizarRol.controller.js +++ b/Roles/Controladores/actualizarRol.controller.js @@ -16,7 +16,6 @@ const repositorio = require('@altertex/rol/repos/repositorioActualizarRol'); exports.actualizarRol = async (req, res) => { const datosActualizacion = req.body.datosRolActualizacion; - if (!datosActualizacion) { return res.status(MENSAJES.PARAMETROS_INVALIDOS.codigo).json({ mensaje: MENSAJES.PARAMETROS_INVALIDOS.mensaje }); } From 97fae6d03f4fdf8211e1867119adecfd35f05794 Mon Sep 17 00:00:00 2001 From: Hiram <147564077+Hiram10tec@users.noreply.github.com> Date: Thu, 5 Jun 2025 17:54:47 -0600 Subject: [PATCH 067/116] Corregir rutas duplicadas --- Categorias/Rutas/indexCategorias.routes.js | 2 -- Roles/Rutas/indexRoles.routes.js | 3 --- 2 files changed, 5 deletions(-) diff --git a/Categorias/Rutas/indexCategorias.routes.js b/Categorias/Rutas/indexCategorias.routes.js index ba97818f..d412692f 100644 --- a/Categorias/Rutas/indexCategorias.routes.js +++ b/Categorias/Rutas/indexCategorias.routes.js @@ -4,7 +4,6 @@ const rutasConsultarListaCategorias = require('@altertex/cat/rutasInd/consultarL const rutasCrearCategoria = require('@altertex/cat/rutasInd/crearCategoria.routes'); const rutasEliminarCategoria = require('@altertex/cat/rutasInd/eliminarCategoria.routes'); const rutasLeerCategoria = require('@altertex/cat/rutasInd/consultarDetalleCategoria.routes'); -const rutasLeerCategoria = require('@altertex/cat/rutasInd/consultarDetalleCategoria.routes'); const rutasActualizarCategoria = require('@altertex/cat/rutasInd/actualizarCategorias.routes'); const RUTAS = require('@altertex/util/const/rutas'); @@ -13,7 +12,6 @@ ruteador.use(RUTAS.CATEGORIAS.BASE, rutasConsultarListaCategorias); ruteador.use(RUTAS.CATEGORIAS.BASE, rutasCrearCategoria); ruteador.use(RUTAS.CATEGORIAS.BASE, rutasEliminarCategoria); ruteador.use(RUTAS.CATEGORIAS.BASE, rutasLeerCategoria); -ruteador.use(RUTAS.CATEGORIAS.BASE, rutasLeerCategoria); ruteador.use(RUTAS.CATEGORIAS.BASE, rutasActualizarCategoria); module.exports = ruteador; diff --git a/Roles/Rutas/indexRoles.routes.js b/Roles/Rutas/indexRoles.routes.js index 20612e25..5f70fd94 100644 --- a/Roles/Rutas/indexRoles.routes.js +++ b/Roles/Rutas/indexRoles.routes.js @@ -25,8 +25,6 @@ const rutasConsultarDetalle = require('@altertex/rol/rutasInd/consultarDetalleRo const rutasActualizarRol = require('@altertex/rol/rutasInd/actualizarRol.routes'); -const rutasConsultarDetalle = require('@altertex/rol/rutasInd/consultarDetalleRol.routes'); - // Importación del archivo de constantes donde están definidas las rutas base del sistema. const RUTAS = require('@altertex/util/const/rutas'); @@ -49,7 +47,6 @@ ruteador.use(RUTAS.ROLES.BASE, rutasConsultarDetalle); ruteador.use(RUTAS.ROLES.BASE, rutasActualizarRol); -ruteador.use(RUTAS.ROLES.BASE, rutasConsultarDetalle); // Exporta el enrutador para ser utilizado en el archivo principal de rutas de la aplicación (por ejemplo: app.js). module.exports = ruteador; \ No newline at end of file From e9719b624c8ecd672339a5c8b2dd50daea8ee577 Mon Sep 17 00:00:00 2001 From: angieriosc Date: Thu, 5 Jun 2025 18:07:46 -0600 Subject: [PATCH 068/116] Feat: controller, rutas, repositorio, mensajes --- .../exportarProductos.controller.js | 70 ++ .../repositorioExportarProducto.js | 17 + .../exportarProductos.routes.js | 77 ++ Productos/Rutas/indexProductos.routes.js | 5 +- Utilidades/Constantes/consultasProductos.js | 24 +- Utilidades/Constantes/mensajesProductos.js | 10 + Utilidades/Constantes/rutas.js | 5 +- package-lock.json | 669 +++++++++++++++++- package.json | 1 + 9 files changed, 871 insertions(+), 7 deletions(-) create mode 100644 Productos/Controladores/exportarProductos.controller.js create mode 100644 Productos/Datos/Repositorios/repositorioExportarProducto.js create mode 100644 Productos/Rutas/RutasIndividuales/exportarProductos.routes.js diff --git a/Productos/Controladores/exportarProductos.controller.js b/Productos/Controladores/exportarProductos.controller.js new file mode 100644 index 00000000..efe3ba3e --- /dev/null +++ b/Productos/Controladores/exportarProductos.controller.js @@ -0,0 +1,70 @@ +const repositorio = require('@altertex/pro/repos/repositorioExportarProducto'); +const { Parser } = require('json2csv'); +const { format } = require('date-fns'); +const MENSAJES_PRODUCTOS = require('@altertex/util/const/mensajesProductos'); + +const ExcelJS = require('exceljs'); + +/** + * Controlador para exportar productos seleccionados de un cliente y retornar CSV como string en JSON. + * + * @async + * @function exportarProductos + * @param {Request} req + * @param {Response} res + * @returns {Response} JSON con mensaje + contenido CSV en texto plano + * @see [RF58 - Exportar Productos](https://codeandco-wiki.netlify.app/docs/next/proyectos/textiles/documentacion/requisitos/RF58) + */ +exports.exportarProductos = async (req, res) => { + try { + const idCliente = parseInt(req.user.clienteSeleccionado); + const idsProducto = req.body.idsProducto; + + if (!Array.isArray(idsProducto) || idsProducto.length === 0) { + return res.status(MENSAJES_PRODUCTOS.PARAMETROS_INVALIDOS.codigo).json({ + mensaje: 'Debes seleccionar al menos un producto para exportar.', + }); + } + + const idsSeleccionados = idsProducto.map((id) => parseInt(id)); + const productos = await repositorio.obtenerProductosExportacion(idCliente, idsSeleccionados); + + if (!productos || productos.length === 0) { + return res.status(MENSAJES_PRODUCTOS.PRODUCTOS_NO_ENCONTRADOS.codigo).json({ + mensaje: MENSAJES_PRODUCTOS.PRODUCTOS_NO_ENCONTRADOS.mensaje, + }); + } + + productos.forEach((prod) => { + prod.fechaCreacion = format(new Date(prod.fechaCreacion), 'dd/MM/yyyy'); + prod.precio = parseFloat(prod.precio).toFixed(2); + }); + + /** const campos = [ + { label: 'ID', value: 'idProducto' }, + { label: 'Nombre', value: 'nombre' }, + { label: 'Descripción', value: 'descripcion' }, + { label: 'Precio', value: 'precio' }, + { label: 'Categoría', value: 'categoria' }, + { label: 'Fecha de creación', value: 'fechaCreacion' }, + { label: 'Estatus', value: 'estatus' } + ]; +*/ + const parser = new Parser({ fields: campos }); + const csv = parser.parse(productos); + const csvConBOM = `\uFEFF${csv}`; + + res.setHeader('Content-Type', 'text/csv'); + res.setHeader( + 'Content-Disposition', + `attachment; filename=productos_${idCliente}_${Date.now()}.csv` + ); + + return res.status(200).send(csvConBOM); + } catch (error) { + console.error('Error al exportar productos:', error); + return res.status(MENSAJES_PRODUCTOS.ERROR_EXPORTACION.codigo).json({ + mensaje: MENSAJES_PRODUCTOS.ERROR_EXPORTACION.mensaje, + }); + } +}; diff --git a/Productos/Datos/Repositorios/repositorioExportarProducto.js b/Productos/Datos/Repositorios/repositorioExportarProducto.js new file mode 100644 index 00000000..e591940c --- /dev/null +++ b/Productos/Datos/Repositorios/repositorioExportarProducto.js @@ -0,0 +1,17 @@ +const correrQuery = require('@altertex/util/ser/correrQuery'); +const CONSULTAS_PRODUCTOS = require('@altertex/util/const/consultasProductos'); + +/** + * Consulta la lista de productos seleccionados de un cliente para exportar en CSV. + * + * @param {number} idCliente + * @param {number[]} idsProducto + * @returns {Promise>} + * + * @see [RF58 - Documentación de requisitos](https://codeandco-wiki.netlify.app/docs/next/proyectos/textiles/documentacion/requisitos/RF58) + */ +exports.obtenerProductosExportacion = (idCliente, idsProducto) => { + const placeholders = idsProducto.map(() => '?').join(', '); + const query = CONSULTAS_PRODUCTOS.OBTENER_DATOS_EXPORTACION.replace('__IDS__', placeholders); + return correrQuery(query, [idCliente, ...idsProducto]); +}; diff --git a/Productos/Rutas/RutasIndividuales/exportarProductos.routes.js b/Productos/Rutas/RutasIndividuales/exportarProductos.routes.js new file mode 100644 index 00000000..5fde8376 --- /dev/null +++ b/Productos/Rutas/RutasIndividuales/exportarProductos.routes.js @@ -0,0 +1,77 @@ +//RF58 - Exportar Productos - https://codeandco-wiki.netlify.app/docs/next/proyectos/textiles/documentacion/requisitos/RF58 + +/** + * @swagger + * /api/productos/exportar: + * post: + * summary: Exporta la lista completa de empleados en formato CSV. + * tags: + * - Productos + * security: + * - ApiKeyAuth: [] + * - BearerAuth: [] + * parameters: + * - in: header + * name: x-api-key + * required: true + * schema: + * type: string + * description: Clave de API + * - in: header + * name: Authorization + * required: true + * schema: + * type: string + * description: Token JWT con formato "Bearer " + * responses: + * 200: + * description: Archivo CSV generado correctamente. + * content: + * text/csv: + * schema: + * type: string + * format: binary + * 204: + * description: No hay productos para exportar. + * content: + * application/json: + * schema: + * type: object + * properties: + * mensaje: + * type: string + * example: No hay productos para exportar. + * 400: + * description: Error interno del servidor al exportar productos. + * content: + * application/json: + * schema: + * type: object + * properties: + * mensaje: + * type: string + * example: Error al exportar la lista de productos. + */ + +const express = require('express'); +const ruteador = express.Router(); +const controlador = require('@altertex/pro/ctrl/exportarProductos.controller'); +const revisarApiKey = require('@altertex/util/inter/revisarApiKey'); +const autorizarToken = require('@altertex/util/inter/autorizarToken'); +const verificarPermisos = require('@altertex/util/inter/verificarPermisos'); +const PERMISOS = require('@altertex/util/const/permisos'); +const RUTAS = require('@altertex/util/const/rutas'); +const validarYSanitizar = require('@altertex/util/inter/validarYSanitizar'); +const limitePeticionesDiarias = require('@altertex/util/inter/limitePeticiones'); + +ruteador.post( + RUTAS.PRODUCTOS.EXPORTAR_PRODUCTOS, + revisarApiKey(), + autorizarToken, + limitePeticionesDiarias, + validarYSanitizar, + verificarPermisos(PERMISOS.EXPORTAR_PRODUCTOS), + controlador.exportarProductos +); + +module.exports = ruteador; diff --git a/Productos/Rutas/indexProductos.routes.js b/Productos/Rutas/indexProductos.routes.js index e10c46a5..678cf014 100644 --- a/Productos/Rutas/indexProductos.routes.js +++ b/Productos/Rutas/indexProductos.routes.js @@ -4,6 +4,7 @@ const rutaConsultarLista = require('@altertex/pro/rutasInd/consultarProductos.ro const rutaEliminar = require('@altertex/pro/rutasInd/eliminarProducto.routes'); const rutaCrearProducto = require('@altertex/pro/rutasInd/crearProducto.routes'); const rutasLeerProducto = require('@altertex/pro/rutasInd/leerProductos.routes'); +const rutasExportarProductos = require('@altertex/pro/rutasInd/exportarProductos.routes'); const RUTAS = require('@altertex/util/const/rutas'); @@ -15,5 +16,7 @@ ruteador.use(RUTAS.PRODUCTOS.BASE, rutaCrearProducto); ruteador.use(RUTAS.PRODUCTOS.BASE, rutaEliminar); // RF[28] Leer producto - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF28] ruteador.use(RUTAS.PRODUCTOS.BASE, rutasLeerProducto); +// RF[58] Exportar Productos - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF58] +ruteador.use(RUTAS.PRODUCTOS.BASE, rutasExportarProductos); -module.exports = ruteador; \ No newline at end of file +module.exports = ruteador; diff --git a/Utilidades/Constantes/consultasProductos.js b/Utilidades/Constantes/consultasProductos.js index 0f133d39..f2c70608 100644 --- a/Utilidades/Constantes/consultasProductos.js +++ b/Utilidades/Constantes/consultasProductos.js @@ -1,3 +1,5 @@ +const { OBTENER_DATOS_EXPORTACION } = require('./consultasEmpleados'); + module.exports = { OBTENER_LISTA: ` SELECT p.idProducto, p.nombreComun, p.precioVenta, p.estado, i.urlImagen @@ -22,8 +24,7 @@ module.exports = { VALUES (?, ?, ?, ?, ?, ?, ?); `, - ELIMINAR_PRODUCTOS: - 'DELETE FROM producto WHERE idProducto IN (?)', + ELIMINAR_PRODUCTOS: 'DELETE FROM producto WHERE idProducto IN (?)', LEER_PRODUCTO: ` SELECT JSON_OBJECT( @@ -75,4 +76,23 @@ module.exports = { WHERE p.idProducto = ? AND p.idCliente = ?; `, + OBTENER_DATOS_EXPORTACION: ` + SELECT + p.idProducto, + p.nombreComun, + p.nombreComercial, + p.descripcion, + p.precioVenta, + p.costo, + p.impuesto, + p.descuento, + p.estado, + pr.nombreCompania AS proveedor, + i.urlImagen AS imagen + FROM producto p + JOIN proveedor pr ON p.idProveedor = pr.idProveedor + JOIN imagen_producto ip ON p.idProducto = ip.idProducto + JOIN imagen i ON ip.idImagen = i.idImagen + WHERE p.idCliente = ? AND p.idProducto IN (__IDS__); + `, }; diff --git a/Utilidades/Constantes/mensajesProductos.js b/Utilidades/Constantes/mensajesProductos.js index 1892c23f..3bd231f6 100644 --- a/Utilidades/Constantes/mensajesProductos.js +++ b/Utilidades/Constantes/mensajesProductos.js @@ -1,3 +1,5 @@ +const { PRODUCTOS } = require('./rutas'); + module.exports = { // 200 - OK CONSULTA_EXITOSA: { @@ -14,6 +16,10 @@ module.exports = { codigo: 204, mensaje: 'No se encontraron productos registrados para el cliente.', }, + PRODUCTOS_NO_ENCONTRADOS: { + codigo: 204, + mensaje: 'No se encontraron productos para exportar.', + }, // 400 - Bad Request PARAMETROS_INVALIDOS: { @@ -92,4 +98,8 @@ module.exports = { codigo: 400, mensaje: 'El producto solicitado no existe.', }, + ERROR_EXPORTACION: { + codigo: 400, + mensaje: 'Error al exportar la lista de productos.', + }, }; diff --git a/Utilidades/Constantes/rutas.js b/Utilidades/Constantes/rutas.js index 65fef528..138cd1d5 100644 --- a/Utilidades/Constantes/rutas.js +++ b/Utilidades/Constantes/rutas.js @@ -40,7 +40,8 @@ module.exports = { CONSULTAR_LISTA: '/consultar-lista', CREAR: '/crear', ELIMINAR_PRODUCTO: '/eliminar', - LEER: '/leer-producto' + LEER: '/leer-producto', + EXPORTAR_PRODUCTOS: '/exportar-productos', }, PROVEEDORES: { BASE: '/proveedores', @@ -104,4 +105,4 @@ module.exports = { ACTUALIZAR: '/actualizar', }, API_DOCS: '/api-docs', -}; \ No newline at end of file +}; diff --git a/package-lock.json b/package-lock.json index 4a0b4268..718dd54f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "cors": "^2.8.5", "date-fns": "^4.1.0", "dotenv": "^16.4.7", + "exceljs": "^4.4.0", "express": "^4.21.2", "helmet": "^8.1.0", "json2csv": "^6.0.0-alpha.2", @@ -1938,6 +1939,47 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@fast-csv/format": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@fast-csv/format/-/format-4.3.5.tgz", + "integrity": "sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==", + "license": "MIT", + "dependencies": { + "@types/node": "^14.0.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.isboolean": "^3.0.3", + "lodash.isequal": "^4.5.0", + "lodash.isfunction": "^3.0.9", + "lodash.isnil": "^4.0.0" + } + }, + "node_modules/@fast-csv/format/node_modules/@types/node": { + "version": "14.18.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", + "license": "MIT" + }, + "node_modules/@fast-csv/parse": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@fast-csv/parse/-/parse-4.3.6.tgz", + "integrity": "sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==", + "license": "MIT", + "dependencies": { + "@types/node": "^14.0.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.groupby": "^4.6.0", + "lodash.isfunction": "^3.0.9", + "lodash.isnil": "^4.0.0", + "lodash.isundefined": "^3.0.1", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/@fast-csv/parse/node_modules/@types/node": { + "version": "14.18.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", + "license": "MIT" + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -4842,6 +4884,59 @@ "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", "license": "MIT" }, + "node_modules/archiver": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", + "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "license": "MIT", + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/are-docs-informative": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", @@ -5145,6 +5240,28 @@ "bcrypt": "bin/bcrypt" } }, + "node_modules/big-integer": { + "version": "1.6.52", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "license": "Unlicense", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/binary": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", + "integrity": "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==", + "license": "MIT", + "dependencies": { + "buffers": "~0.1.1", + "chainsaw": "~0.1.0" + }, + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -5157,6 +5274,55 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/blessed": { "version": "0.1.81", "resolved": "https://registry.npmjs.org/blessed/-/blessed-0.1.81.tgz", @@ -5169,6 +5335,12 @@ "node": ">= 0.8.0" } }, + "node_modules/bluebird": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", + "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==", + "license": "MIT" + }, "node_modules/bodec": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/bodec/-/bodec-0.1.0.tgz", @@ -5281,6 +5453,15 @@ "isarray": "^1.0.0" } }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -5293,6 +5474,23 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "license": "MIT" }, + "node_modules/buffer-indexof-polyfill": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz", + "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/buffers": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", + "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==", + "engines": { + "node": ">=0.2.0" + } + }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -5406,6 +5604,18 @@ ], "license": "CC-BY-4.0" }, + "node_modules/chainsaw": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", + "integrity": "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==", + "license": "MIT/X11", + "dependencies": { + "traverse": ">=0.3.0 <0.4" + }, + "engines": { + "node": "*" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -5664,6 +5874,35 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/compress-commons": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", + "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", + "license": "MIT", + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/compress-commons/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -5786,6 +6025,45 @@ "node": ">= 0.10" } }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", + "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/crc32-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/create-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", @@ -6083,6 +6361,15 @@ "node": ">= 0.4" } }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "license": "BSD-3-Clause", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -6133,6 +6420,15 @@ "node": ">= 0.8" } }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/enquirer": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", @@ -6539,6 +6835,49 @@ "node": ">=18.0.0" } }, + "node_modules/exceljs": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/exceljs/-/exceljs-4.4.0.tgz", + "integrity": "sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg==", + "license": "MIT", + "dependencies": { + "archiver": "^5.0.0", + "dayjs": "^1.8.34", + "fast-csv": "^4.3.1", + "jszip": "^3.10.1", + "readable-stream": "^3.6.0", + "saxes": "^5.0.1", + "tmp": "^0.2.0", + "unzipper": "^0.10.11", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=8.3.0" + } + }, + "node_modules/exceljs/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/exceljs/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -6669,6 +7008,19 @@ "follow-redirects": "^1.14.0" } }, + "node_modules/fast-csv": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/fast-csv/-/fast-csv-4.3.6.tgz", + "integrity": "sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==", + "license": "MIT", + "dependencies": { + "@fast-csv/format": "4.3.5", + "@fast-csv/parse": "4.3.6" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -6936,6 +7288,12 @@ "node": ">= 0.6" } }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -6956,6 +7314,22 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + }, + "engines": { + "node": ">=0.6" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -7107,7 +7481,6 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -7166,7 +7539,6 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, "license": "ISC" }, "node_modules/has-flag": { @@ -7425,6 +7797,12 @@ "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", "license": "ISC" }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, "node_modules/immutable": { "version": "3.8.2", "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz", @@ -8640,6 +9018,24 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, "node_modules/jwa": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", @@ -8690,6 +9086,18 @@ "node": ">=0.2.0" } }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "license": "MIT", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -8714,6 +9122,15 @@ "node": ">= 0.8.0" } }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -8721,6 +9138,12 @@ "dev": true, "license": "MIT" }, + "node_modules/listenercount": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", + "integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==", + "license": "ISC" + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -8749,6 +9172,30 @@ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "license": "MIT" }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "license": "MIT" + }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", + "license": "MIT" + }, + "node_modules/lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", + "license": "MIT" + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "license": "MIT" + }, "node_modules/lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", @@ -8756,6 +9203,12 @@ "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.", "license": "MIT" }, + "node_modules/lodash.groupby": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz", + "integrity": "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==", + "license": "MIT" + }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -8775,12 +9228,24 @@ "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", "license": "MIT" }, + "node_modules/lodash.isfunction": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", + "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==", + "license": "MIT" + }, "node_modules/lodash.isinteger": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", "license": "MIT" }, + "node_modules/lodash.isnil": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/lodash.isnil/-/lodash.isnil-4.0.0.tgz", + "integrity": "sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==", + "license": "MIT" + }, "node_modules/lodash.isnumber": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", @@ -8799,6 +9264,12 @@ "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", "license": "MIT" }, + "node_modules/lodash.isundefined": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz", + "integrity": "sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==", + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -8818,6 +9289,18 @@ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", "license": "MIT" }, + "node_modules/lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", + "license": "MIT" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "license": "MIT" + }, "node_modules/long": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", @@ -10814,6 +11297,36 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "license": "MIT" }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -11056,6 +11569,19 @@ "node": ">=4" } }, + "node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", @@ -11200,6 +11726,18 @@ "integrity": "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==", "license": "ISC" }, + "node_modules/saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/scheduler": { "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", @@ -11330,6 +11868,12 @@ "node": ">= 0.4" } }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -12096,6 +12640,36 @@ "url": "https://www.buymeacoffee.com/systeminfo" } }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -12111,6 +12685,15 @@ "node": ">=8" } }, + "node_modules/tmp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -12154,6 +12737,15 @@ "nodetouch": "bin/nodetouch.js" } }, + "node_modules/traverse": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", + "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==", + "license": "MIT/X11", + "engines": { + "node": "*" + } + }, "node_modules/tree-sitter": { "version": "0.21.1", "resolved": "https://registry.npmjs.org/tree-sitter/-/tree-sitter-0.21.1.tgz", @@ -12325,6 +12917,24 @@ "integrity": "sha512-08/DA66UF65OlpUDIQtbJyrqTR0jTAlJ+jsnkQ4jxR7+K5g5YG1APZKQSMCE1vqqmD+2pv6+IdEjmopFatacvg==", "license": "MIT" }, + "node_modules/unzipper": { + "version": "0.10.14", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.14.tgz", + "integrity": "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==", + "license": "MIT", + "dependencies": { + "big-integer": "^1.6.17", + "binary": "~0.3.0", + "bluebird": "~3.4.1", + "buffer-indexof-polyfill": "~1.0.0", + "duplexer2": "~0.1.4", + "fstream": "^1.0.12", + "graceful-fs": "^4.2.2", + "listenercount": "~1.0.1", + "readable-stream": "~2.3.6", + "setimmediate": "~1.0.4" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", @@ -12674,6 +13284,12 @@ "node": ">=4.0" } }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "license": "MIT" + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -12787,6 +13403,55 @@ "integrity": "sha512-jEA1znR7b4C/NnaycInCU6h/d15ZzCd1jmsruqOKnZP6WXQSMH3W2GL+OXbkruslU4h+Tzuos0HdswzRUk/Vgg==", "license": "Unlicense" }, + "node_modules/zip-stream": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", + "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^3.0.4", + "compress-commons": "^4.1.2", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/archiver-utils": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", + "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", + "license": "MIT", + "dependencies": { + "glob": "^7.2.3", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/zod": { "version": "3.24.4", "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.4.tgz", diff --git a/package.json b/package.json index be413ca4..68d15d58 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "cors": "^2.8.5", "date-fns": "^4.1.0", "dotenv": "^16.4.7", + "exceljs": "^4.4.0", "express": "^4.21.2", "helmet": "^8.1.0", "json2csv": "^6.0.0-alpha.2", From b9b44dd15765c6ef572c982a91293280cbd07b33 Mon Sep 17 00:00:00 2001 From: Hiram <147564077+Hiram10tec@users.noreply.github.com> Date: Thu, 5 Jun 2025 18:08:43 -0600 Subject: [PATCH 069/116] =?UTF-8?q?correcci=C3=B3n=20lint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Utilidades/Constantes/consultasCategorias.js | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/Utilidades/Constantes/consultasCategorias.js b/Utilidades/Constantes/consultasCategorias.js index 70c38b59..9d9f34c9 100644 --- a/Utilidades/Constantes/consultasCategorias.js +++ b/Utilidades/Constantes/consultasCategorias.js @@ -60,18 +60,6 @@ module.exports = { WHERE c.idCategoria = ?; `, - LEER_DETALLE_CATEGORIA: ` - SELECT - c.idCategoria, - c.nombreCategoria, - c.descripcion, - p.idProducto, - p.nombreComun - FROM categoria c - LEFT JOIN categoria_producto cp ON c.idCategoria = cp.idCategoria - LEFT JOIN producto p ON cp.idProducto = p.idProducto - WHERE c.idCategoria = ?; - `, ACTUALIZAR_CATEGORIA: ` UPDATE categoria SET nombreCategoria = ?, descripcion = ? From 3134acb396adb052db8bf3f9d3e223a0458bd47c Mon Sep 17 00:00:00 2001 From: angieriosc Date: Thu, 5 Jun 2025 18:47:51 -0600 Subject: [PATCH 070/116] Feat: consulta productos completa, controller con las hojas de productos, variantes, opciones --- .../exportarProductos.controller.js | 104 ++++++++++++++---- Utilidades/Constantes/consultasProductos.js | 43 ++++++-- 2 files changed, 117 insertions(+), 30 deletions(-) diff --git a/Productos/Controladores/exportarProductos.controller.js b/Productos/Controladores/exportarProductos.controller.js index efe3ba3e..788c0a43 100644 --- a/Productos/Controladores/exportarProductos.controller.js +++ b/Productos/Controladores/exportarProductos.controller.js @@ -35,32 +35,98 @@ exports.exportarProductos = async (req, res) => { }); } - productos.forEach((prod) => { - prod.fechaCreacion = format(new Date(prod.fechaCreacion), 'dd/MM/yyyy'); - prod.precio = parseFloat(prod.precio).toFixed(2); + const workbook = new ExcelJS.Workbook(); + + // Primera hoja - Información básica del producto + const hoja1 = workbook.addWorksheet('Información Producto'); + hoja1.columns = [ + { header: 'ID Producto', key: 'idProducto' }, + { header: 'ID Proveedor', key: 'idProveedor' }, + { header: 'Nombre Producto', key: 'nombreProducto' }, + { header: 'Nombre Comercial', key: 'nombreComercial' }, + { header: 'Descripción', key: 'descripcionProducto' }, + { header: 'Tipo Producto', key: 'tipoProducto' }, + { header: 'Marca', key: 'marca' }, + { header: 'Modelo', key: 'modelo' }, + { header: 'Costo', key: 'costo' }, + { header: 'Precio Venta', key: 'precioVenta' }, + { header: 'Precio Cliente', key: 'precioCliente' }, + { header: 'Precio Puntos', key: 'precioPuntos' }, + { header: 'Impuesto', key: 'impuesto' }, + { header: 'Descuento', key: 'descuento' }, + { header: 'Estado', key: 'estado' }, + { header: 'Envío', key: 'envio' }, + ]; + hoja1.addRows(productos); + + // Segunda hoja - Información desglosada de variantes + const hoja2 = workbook.addWorksheet('Variantes'); + hoja2.columns = [ + { header: 'ID Producto', key: 'idProducto' }, + { header: 'Nombre Producto', key: 'nombreProducto' }, + { header: 'Nombre Variante', key: 'nombreVariante' }, + { header: 'Descripción Variante', key: 'descripcionVariante' }, + ]; + + // Procesar las variantes para la segunda hoja + const variantesDesglosadas = productos.flatMap((producto) => { + const variantes = producto.variantes_opciones.split(' | ').map((variante) => { + const [datosVariante] = variante.split(','); + const [nombreVariante, descripcionVariante] = datosVariante.split('-'); + return { + idProducto: producto.idProducto, + nombreProducto: producto.nombreProducto, + nombreVariante, + descripcionVariante, + }; + }); + return variantes; }); + hoja2.addRows(variantesDesglosadas); - /** const campos = [ - { label: 'ID', value: 'idProducto' }, - { label: 'Nombre', value: 'nombre' }, - { label: 'Descripción', value: 'descripcion' }, - { label: 'Precio', value: 'precio' }, - { label: 'Categoría', value: 'categoria' }, - { label: 'Fecha de creación', value: 'fechaCreacion' }, - { label: 'Estatus', value: 'estatus' } + // Tercera hoja - Información desglosada de opciones + const hoja3 = workbook.addWorksheet('Opciones'); + hoja3.columns = [ + { header: 'ID Producto', key: 'idProducto' }, + { header: 'Nombre Producto', key: 'nombreProducto' }, + { header: 'Nombre Variante', key: 'nombreVariante' }, + { header: 'Valor Opción', key: 'valorOpcion' }, + { header: 'SKU Comercial', key: 'skuComercial' }, + { header: 'Cantidad', key: 'cantidad' }, ]; -*/ - const parser = new Parser({ fields: campos }); - const csv = parser.parse(productos); - const csvConBOM = `\uFEFF${csv}`; - res.setHeader('Content-Type', 'text/csv'); + // Procesar las opciones para la tercera hoja + const opcionesDesglosadas = productos.flatMap((producto) => { + return producto.variantes_opciones.split(' | ').flatMap((variante) => { + const [datosVariante, opcionesStr] = variante.split(','); + const [nombreVariante] = datosVariante.split('-'); + + if (!opcionesStr) return []; + + return opcionesStr.split(', ').map((opcion) => { + const [valorOpcion, skuComercial, cantidad] = opcion.split('-'); + return { + idProducto: producto.idProducto, + nombreProducto: producto.nombreProducto, + nombreVariante, + valorOpcion, + skuComercial, + cantidad, + }; + }); + }); + }); + hoja3.addRows(opcionesDesglosadas); + + const buffer = await workbook.xlsx.writeBuffer(); + + res.setHeader('Content-Disposition', 'attachment; filename=productos.xlsx'); res.setHeader( - 'Content-Disposition', - `attachment; filename=productos_${idCliente}_${Date.now()}.csv` + 'Content-Type', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ); - return res.status(200).send(csvConBOM); + return res.send(buffer); } catch (error) { console.error('Error al exportar productos:', error); return res.status(MENSAJES_PRODUCTOS.ERROR_EXPORTACION.codigo).json({ diff --git a/Utilidades/Constantes/consultasProductos.js b/Utilidades/Constantes/consultasProductos.js index f2c70608..69851dba 100644 --- a/Utilidades/Constantes/consultasProductos.js +++ b/Utilidades/Constantes/consultasProductos.js @@ -77,22 +77,43 @@ module.exports = { AND p.idCliente = ?; `, OBTENER_DATOS_EXPORTACION: ` - SELECT + SELECT p.idProducto, - p.nombreComun, + p.idProveedor, + p.nombreComun AS nombreProducto, p.nombreComercial, - p.descripcion, - p.precioVenta, + p.descripcion AS descripcionProducto, + p.tipoProducto, + p.marca, + p.modelo, p.costo, + p.precioVenta, + p.precioCliente, + p.precioPuntos, p.impuesto, p.descuento, p.estado, - pr.nombreCompania AS proveedor, - i.urlImagen AS imagen - FROM producto p - JOIN proveedor pr ON p.idProveedor = pr.idProveedor - JOIN imagen_producto ip ON p.idProducto = ip.idProducto - JOIN imagen i ON ip.idImagen = i.idImagen - WHERE p.idCliente = ? AND p.idProducto IN (__IDS__); + p.envio, + GROUP_CONCAT( + CONCAT( + v.nombreVariante, '-', + v.descripcion, ',', + (SELECT GROUP_CONCAT( + CONCAT(o.valorOpcion, '-', o.SKUcomercial, '-', o.cantidad) + SEPARATOR ', ' + ) + FROM opcion o + WHERE o.idVariante = v.idVariante + ) + ) + SEPARATOR ' | ' + ) AS variantes_opciones + FROM producto p + JOIN variante v ON v.idProducto = p.idProducto + WHERE p.idCliente = ? AND p.idProducto IN (__IDS__) + GROUP BY p.idProducto, p.idProveedor, p.nombreComun, p.nombreComercial, + p.descripcion, p.tipoProducto, p.marca, p.modelo, p.costo, + p.precioVenta, p.precioCliente, p.precioPuntos, p.impuesto, + p.descuento, p.estado, p.envio; `, }; From 496b9e34d5182b863680742d10ee930b905af5f8 Mon Sep 17 00:00:00 2001 From: angieriosc Date: Thu, 5 Jun 2025 19:55:57 -0600 Subject: [PATCH 071/116] Feat: Agregar SKU automatica, y corregir parseo de productos --- .../exportarProductos.controller.js | 45 ++++++++++++------- Utilidades/Constantes/consultasProductos.js | 2 +- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/Productos/Controladores/exportarProductos.controller.js b/Productos/Controladores/exportarProductos.controller.js index 788c0a43..a4e34279 100644 --- a/Productos/Controladores/exportarProductos.controller.js +++ b/Productos/Controladores/exportarProductos.controller.js @@ -34,7 +34,6 @@ exports.exportarProductos = async (req, res) => { mensaje: MENSAJES_PRODUCTOS.PRODUCTOS_NO_ENCONTRADOS.mensaje, }); } - const workbook = new ExcelJS.Workbook(); // Primera hoja - Información básica del producto @@ -92,30 +91,44 @@ exports.exportarProductos = async (req, res) => { { header: 'Nombre Variante', key: 'nombreVariante' }, { header: 'Valor Opción', key: 'valorOpcion' }, { header: 'SKU Comercial', key: 'skuComercial' }, + { header: 'SKU Automático', key: 'skuAutomatico' }, { header: 'Cantidad', key: 'cantidad' }, ]; - // Procesar las opciones para la tercera hoja const opcionesDesglosadas = productos.flatMap((producto) => { return producto.variantes_opciones.split(' | ').flatMap((variante) => { - const [datosVariante, opcionesStr] = variante.split(','); + // Separar el encabezado de la variante y sus opciones + const [datosVariante, ...opcionesParts] = variante.split(','); const [nombreVariante] = datosVariante.split('-'); - if (!opcionesStr) return []; - - return opcionesStr.split(', ').map((opcion) => { - const [valorOpcion, skuComercial, cantidad] = opcion.split('-'); - return { - idProducto: producto.idProducto, - nombreProducto: producto.nombreProducto, - nombreVariante, - valorOpcion, - skuComercial, - cantidad, - }; - }); + // Combinar todas las partes de opciones y dividirlas correctamente + const opcionesCompletas = opcionesParts.join(',').trim(); + + // Si no hay opciones, retornar array vacío + if (!opcionesCompletas) return []; + + // Dividir las opciones y filtrar elementos vacíos + return opcionesCompletas + .split(', ') + .filter((opcion) => opcion.trim()) + .map((opcion) => { + const [valorOpcion, skuComercial, skuAutomatico, cantidad] = opcion + .split(':') + .map((s) => s.trim()); + + return { + idProducto: producto.idProducto, + nombreProducto: producto.nombreProducto, + nombreVariante, + valorOpcion, + skuComercial, + skuAutomatico, + cantidad, + }; + }); }); }); + hoja3.addRows(opcionesDesglosadas); const buffer = await workbook.xlsx.writeBuffer(); diff --git a/Utilidades/Constantes/consultasProductos.js b/Utilidades/Constantes/consultasProductos.js index 69851dba..c562d149 100644 --- a/Utilidades/Constantes/consultasProductos.js +++ b/Utilidades/Constantes/consultasProductos.js @@ -99,7 +99,7 @@ module.exports = { v.nombreVariante, '-', v.descripcion, ',', (SELECT GROUP_CONCAT( - CONCAT(o.valorOpcion, '-', o.SKUcomercial, '-', o.cantidad) + CONCAT(o.valorOpcion, ':', o.SKUcomercial, ':', o.SKUautomatico, ':', o.cantidad) SEPARATOR ', ' ) FROM opcion o From 513f18ec5346b1dc32c0ef4bab649f93275c90ef Mon Sep 17 00:00:00 2001 From: max Date: Thu, 5 Jun 2025 20:10:08 -0600 Subject: [PATCH 072/116] Co-authored-by: Nicolas Hood Figueroa --- .../actualizarUsuario.controller.js | 2 + .../repositorioActualizarUsuario.js | 68 +++++++++++++------ Utilidades/Constantes/consultasUsuarios.js | 45 +++++++----- 3 files changed, 78 insertions(+), 37 deletions(-) diff --git a/Usuarios/Controladores/actualizarUsuario.controller.js b/Usuarios/Controladores/actualizarUsuario.controller.js index 6e58b502..b071e318 100644 --- a/Usuarios/Controladores/actualizarUsuario.controller.js +++ b/Usuarios/Controladores/actualizarUsuario.controller.js @@ -1,5 +1,7 @@ const MENSAJES = require('@altertex/util/const/mensajesUsuarios'); const repositorio = require('@altertex/usu/repos/repositorioActualizarUsuario'); +const bcrypt = require('bcryptjs'); + //RF[4] Actualizar Usuario - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF4] /** diff --git a/Usuarios/Datos/Repositorios/repositorioActualizarUsuario.js b/Usuarios/Datos/Repositorios/repositorioActualizarUsuario.js index 9cd79266..cd216091 100644 --- a/Usuarios/Datos/Repositorios/repositorioActualizarUsuario.js +++ b/Usuarios/Datos/Repositorios/repositorioActualizarUsuario.js @@ -40,29 +40,57 @@ exports.actualizarUsuario = async (datos) => { idCliente, } = usuario; - // Actualiza datos del usuario - await correrQuery(CONSULTAS_USUARIOS.ACTUALIZAR_DATOS_USUARIO, [ - nombreCompleto, - correoElectronico, - contrasenia, - numeroTelefono, - direccion, - fechaNacimiento, - genero, - estatus, - idUsuario, - ]); + const conContrasena = () => (usuario.contrasenia == '' ? false : true); - if (idCliente !== undefined) { - // Intenta actualizar primero - const resultado = await correrQuery(CONSULTAS_USUARIOS.ACTUALIZAR_CLIENTE_USUARIO, [ - idCliente, + // Actualiza datos del usuario + if (conContrasena()) { + await correrQuery(CONSULTAS_USUARIOS.ACTUALIZAR_DATOS_USUARIO, [ + nombreCompleto, + correoElectronico, + contrasenia, + numeroTelefono, + direccion, + fechaNacimiento, + genero, + estatus, + idUsuario, + ]); + } else { + await correrQuery(CONSULTAS_USUARIOS.ACTUALIZAR_DATOS_USUARIO_SIN_CONTRASENA, [ + nombreCompleto, + correoElectronico, + numeroTelefono, + direccion, + fechaNacimiento, + genero, + estatus, idUsuario, ]); - // Si no se actualizó ninguna fila, inserta la relación - if (resultado.affectedRows === 0) { - await correrQuery(CONSULTAS_USUARIOS.ASOCIAR_USUARIO_A_CLIENTE, [idUsuario, idCliente]); - } + } + + if (usuario.cliente) { + // Pasar el cliente/clientes a un array + const clientes = Array.isArray(usuario.cliente) ? usuario.cliente : [usuario.cliente]; + + await correrQuery(CONSULTAS_USUARIOS.ELIMINAR_USUARIO_CLIENTE, [idUsuario]).then( + // Eliminar todos los cliente asociados al usuario + async () => { + try { + for (const cliente of clientes) { + // Asociar cada cliente seleccionado al usuario + if (cliente) { + // Evitar errores si el cliente es undefined o null + await correrQuery(CONSULTAS_USUARIOS.ASOCIAR_USUARIO_A_CLIENTE, [ + idUsuario, + cliente, + ]); + } + } + } catch (error) { + return error; + } + } + ); } }) ); diff --git a/Utilidades/Constantes/consultasUsuarios.js b/Utilidades/Constantes/consultasUsuarios.js index c7589a64..f263f80a 100644 --- a/Utilidades/Constantes/consultasUsuarios.js +++ b/Utilidades/Constantes/consultasUsuarios.js @@ -40,6 +40,11 @@ module.exports = { VALUES (?, ?); `, + BORRAR_ASOCIACIONES_CLIENTE_USUARIO: ` + DELETE FROM usuario_cliente + WHERE idUsuario = ?; + `, + ASOCIAR_USUARIO_A_CLIENTE: ` INSERT INTO usuario_cliente (idUsuario, idCliente) VALUES (?, ?); @@ -90,28 +95,34 @@ module.exports = { `, ACTUALIZAR_DATOS_USUARIO: ` -UPDATE usuario SET - nombreCompleto = ?, - correoElectronico = ?, - contrasenia = ?, - numeroTelefono = ?, - direccion = ?, - fechaNacimiento = ?, - genero = ?, - estatus = ? -WHERE idUsuario = ?; + UPDATE usuario SET + nombreCompleto = ?, + correoElectronico = ?, + contrasenia = ?, + numeroTelefono = ?, + direccion = ?, + fechaNacimiento = ?, + genero = ?, + estatus = ? + WHERE idUsuario = ?; `, - ACTUALIZAR_CLIENTE_USUARIO: ` -UPDATE usuario_cliente SET - idCliente = ? -WHERE idUsuario = ?; + ACTUALIZAR_DATOS_USUARIO_SIN_CONTRASENA: ` + UPDATE usuario SET + nombreCompleto = ?, + correoElectronico = ?, + numeroTelefono = ?, + direccion = ?, + fechaNacimiento = ?, + genero = ?, + estatus = ? + WHERE idUsuario = ?; `, ACTUALIZAR_ROL_USUARIO: ` -UPDATE usuario_rol SET -idRol = ? -WHERE idUsuario = ?; + UPDATE usuario_rol SET + idRol = ? + WHERE idUsuario = ?; `, VALIDAR_CORREO: ` From fe385dc5572aa5b5fe7eecd53039412bf283e871 Mon Sep 17 00:00:00 2001 From: angieriosc Date: Thu, 5 Jun 2025 20:11:33 -0600 Subject: [PATCH 073/116] Fix: correciones eslint --- Productos/Controladores/exportarProductos.controller.js | 4 +--- Utilidades/Constantes/consultasProductos.js | 2 -- Utilidades/Constantes/mensajesProductos.js | 2 -- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/Productos/Controladores/exportarProductos.controller.js b/Productos/Controladores/exportarProductos.controller.js index a4e34279..73aeb0ab 100644 --- a/Productos/Controladores/exportarProductos.controller.js +++ b/Productos/Controladores/exportarProductos.controller.js @@ -1,6 +1,4 @@ const repositorio = require('@altertex/pro/repos/repositorioExportarProducto'); -const { Parser } = require('json2csv'); -const { format } = require('date-fns'); const MENSAJES_PRODUCTOS = require('@altertex/util/const/mensajesProductos'); const ExcelJS = require('exceljs'); @@ -114,7 +112,7 @@ exports.exportarProductos = async (req, res) => { .map((opcion) => { const [valorOpcion, skuComercial, skuAutomatico, cantidad] = opcion .split(':') - .map((s) => s.trim()); + .map((valores) => valores.trim()); return { idProducto: producto.idProducto, diff --git a/Utilidades/Constantes/consultasProductos.js b/Utilidades/Constantes/consultasProductos.js index c562d149..b0c2d545 100644 --- a/Utilidades/Constantes/consultasProductos.js +++ b/Utilidades/Constantes/consultasProductos.js @@ -1,5 +1,3 @@ -const { OBTENER_DATOS_EXPORTACION } = require('./consultasEmpleados'); - module.exports = { OBTENER_LISTA: ` SELECT p.idProducto, p.nombreComun, p.precioVenta, p.estado, i.urlImagen diff --git a/Utilidades/Constantes/mensajesProductos.js b/Utilidades/Constantes/mensajesProductos.js index 3bd231f6..7f713977 100644 --- a/Utilidades/Constantes/mensajesProductos.js +++ b/Utilidades/Constantes/mensajesProductos.js @@ -1,5 +1,3 @@ -const { PRODUCTOS } = require('./rutas'); - module.exports = { // 200 - OK CONSULTA_EXITOSA: { From f5e8ccef72fc9d6376d87f6ef8ad842e3968a982 Mon Sep 17 00:00:00 2001 From: angieriosc Date: Thu, 5 Jun 2025 20:44:40 -0600 Subject: [PATCH 074/116] Fix: pasar logica a repositorio --- .../exportarProductos.controller.js | 104 +-------------- .../repositorioExportarProducto.js | 118 ++++++++++++++++++ 2 files changed, 121 insertions(+), 101 deletions(-) diff --git a/Productos/Controladores/exportarProductos.controller.js b/Productos/Controladores/exportarProductos.controller.js index 73aeb0ab..ebdd243c 100644 --- a/Productos/Controladores/exportarProductos.controller.js +++ b/Productos/Controladores/exportarProductos.controller.js @@ -1,16 +1,14 @@ const repositorio = require('@altertex/pro/repos/repositorioExportarProducto'); const MENSAJES_PRODUCTOS = require('@altertex/util/const/mensajesProductos'); -const ExcelJS = require('exceljs'); - /** - * Controlador para exportar productos seleccionados de un cliente y retornar CSV como string en JSON. + * Controlador para exportar productos seleccionados de un cliente y retornar Excel. * * @async * @function exportarProductos * @param {Request} req * @param {Response} res - * @returns {Response} JSON con mensaje + contenido CSV en texto plano + * @returns {Response} Archivo Excel con los productos exportados * @see [RF58 - Exportar Productos](https://codeandco-wiki.netlify.app/docs/next/proyectos/textiles/documentacion/requisitos/RF58) */ exports.exportarProductos = async (req, res) => { @@ -32,104 +30,8 @@ exports.exportarProductos = async (req, res) => { mensaje: MENSAJES_PRODUCTOS.PRODUCTOS_NO_ENCONTRADOS.mensaje, }); } - const workbook = new ExcelJS.Workbook(); - - // Primera hoja - Información básica del producto - const hoja1 = workbook.addWorksheet('Información Producto'); - hoja1.columns = [ - { header: 'ID Producto', key: 'idProducto' }, - { header: 'ID Proveedor', key: 'idProveedor' }, - { header: 'Nombre Producto', key: 'nombreProducto' }, - { header: 'Nombre Comercial', key: 'nombreComercial' }, - { header: 'Descripción', key: 'descripcionProducto' }, - { header: 'Tipo Producto', key: 'tipoProducto' }, - { header: 'Marca', key: 'marca' }, - { header: 'Modelo', key: 'modelo' }, - { header: 'Costo', key: 'costo' }, - { header: 'Precio Venta', key: 'precioVenta' }, - { header: 'Precio Cliente', key: 'precioCliente' }, - { header: 'Precio Puntos', key: 'precioPuntos' }, - { header: 'Impuesto', key: 'impuesto' }, - { header: 'Descuento', key: 'descuento' }, - { header: 'Estado', key: 'estado' }, - { header: 'Envío', key: 'envio' }, - ]; - hoja1.addRows(productos); - - // Segunda hoja - Información desglosada de variantes - const hoja2 = workbook.addWorksheet('Variantes'); - hoja2.columns = [ - { header: 'ID Producto', key: 'idProducto' }, - { header: 'Nombre Producto', key: 'nombreProducto' }, - { header: 'Nombre Variante', key: 'nombreVariante' }, - { header: 'Descripción Variante', key: 'descripcionVariante' }, - ]; - - // Procesar las variantes para la segunda hoja - const variantesDesglosadas = productos.flatMap((producto) => { - const variantes = producto.variantes_opciones.split(' | ').map((variante) => { - const [datosVariante] = variante.split(','); - const [nombreVariante, descripcionVariante] = datosVariante.split('-'); - return { - idProducto: producto.idProducto, - nombreProducto: producto.nombreProducto, - nombreVariante, - descripcionVariante, - }; - }); - return variantes; - }); - hoja2.addRows(variantesDesglosadas); - - // Tercera hoja - Información desglosada de opciones - const hoja3 = workbook.addWorksheet('Opciones'); - hoja3.columns = [ - { header: 'ID Producto', key: 'idProducto' }, - { header: 'Nombre Producto', key: 'nombreProducto' }, - { header: 'Nombre Variante', key: 'nombreVariante' }, - { header: 'Valor Opción', key: 'valorOpcion' }, - { header: 'SKU Comercial', key: 'skuComercial' }, - { header: 'SKU Automático', key: 'skuAutomatico' }, - { header: 'Cantidad', key: 'cantidad' }, - ]; - // Procesar las opciones para la tercera hoja - const opcionesDesglosadas = productos.flatMap((producto) => { - return producto.variantes_opciones.split(' | ').flatMap((variante) => { - // Separar el encabezado de la variante y sus opciones - const [datosVariante, ...opcionesParts] = variante.split(','); - const [nombreVariante] = datosVariante.split('-'); - - // Combinar todas las partes de opciones y dividirlas correctamente - const opcionesCompletas = opcionesParts.join(',').trim(); - - // Si no hay opciones, retornar array vacío - if (!opcionesCompletas) return []; - - // Dividir las opciones y filtrar elementos vacíos - return opcionesCompletas - .split(', ') - .filter((opcion) => opcion.trim()) - .map((opcion) => { - const [valorOpcion, skuComercial, skuAutomatico, cantidad] = opcion - .split(':') - .map((valores) => valores.trim()); - - return { - idProducto: producto.idProducto, - nombreProducto: producto.nombreProducto, - nombreVariante, - valorOpcion, - skuComercial, - skuAutomatico, - cantidad, - }; - }); - }); - }); - - hoja3.addRows(opcionesDesglosadas); - const buffer = await workbook.xlsx.writeBuffer(); + const buffer = await repositorio.generarArchivoExcel(productos); res.setHeader('Content-Disposition', 'attachment; filename=productos.xlsx'); res.setHeader( diff --git a/Productos/Datos/Repositorios/repositorioExportarProducto.js b/Productos/Datos/Repositorios/repositorioExportarProducto.js index e591940c..69b7af2c 100644 --- a/Productos/Datos/Repositorios/repositorioExportarProducto.js +++ b/Productos/Datos/Repositorios/repositorioExportarProducto.js @@ -1,5 +1,6 @@ const correrQuery = require('@altertex/util/ser/correrQuery'); const CONSULTAS_PRODUCTOS = require('@altertex/util/const/consultasProductos'); +const excelJS = require('exceljs'); /** * Consulta la lista de productos seleccionados de un cliente para exportar en CSV. @@ -15,3 +16,120 @@ exports.obtenerProductosExportacion = (idCliente, idsProducto) => { const query = CONSULTAS_PRODUCTOS.OBTENER_DATOS_EXPORTACION.replace('__IDS__', placeholders); return correrQuery(query, [idCliente, ...idsProducto]); }; + +/** + * Procesa y genera el archivo Excel con los productos exportados + * @param {Array} productos Lista de productos a exportar + * @returns {Promise} Buffer con el archivo Excel + */ +exports.generarArchivoExcel = async (productos) => { + const workbook = new excelJS.Workbook(); + + // Primera hoja - Información básica del producto + const hoja1 = workbook.addWorksheet('Información Producto'); + hoja1.columns = [ + { header: 'ID Producto', key: 'idProducto' }, + { header: 'ID Proveedor', key: 'idProveedor' }, + { header: 'Nombre Producto', key: 'nombreProducto' }, + { header: 'Nombre Comercial', key: 'nombreComercial' }, + { header: 'Descripción', key: 'descripcionProducto' }, + { header: 'Tipo Producto', key: 'tipoProducto' }, + { header: 'Marca', key: 'marca' }, + { header: 'Modelo', key: 'modelo' }, + { header: 'Costo', key: 'costo' }, + { header: 'Precio Venta', key: 'precioVenta' }, + { header: 'Precio Cliente', key: 'precioCliente' }, + { header: 'Precio Puntos', key: 'precioPuntos' }, + { header: 'Impuesto', key: 'impuesto' }, + { header: 'Descuento', key: 'descuento' }, + { header: 'Estado', key: 'estado' }, + { header: 'Envío', key: 'envio' }, + ]; + hoja1.addRows(productos); + + // Segunda hoja - Información desglosada de variantes + const hoja2 = workbook.addWorksheet('Variantes'); + hoja2.columns = [ + { header: 'ID Producto', key: 'idProducto' }, + { header: 'Nombre Producto', key: 'nombreProducto' }, + { header: 'Nombre Variante', key: 'nombreVariante' }, + { header: 'Descripción Variante', key: 'descripcionVariante' }, + ]; + + const variantesDesglosadas = procesarVariantes(productos); + hoja2.addRows(variantesDesglosadas); + + // Tercera hoja - Información desglosada de opciones + const hoja3 = workbook.addWorksheet('Opciones'); + hoja3.columns = [ + { header: 'ID Producto', key: 'idProducto' }, + { header: 'Nombre Producto', key: 'nombreProducto' }, + { header: 'Nombre Variante', key: 'nombreVariante' }, + { header: 'Valor Opción', key: 'valorOpcion' }, + { header: 'SKU Comercial', key: 'skuComercial' }, + { header: 'SKU Automático', key: 'skuAutomatico' }, + { header: 'Cantidad', key: 'cantidad' }, + ]; + + const opcionesDesglosadas = procesarOpciones(productos); + hoja3.addRows(opcionesDesglosadas); + + return await workbook.xlsx.writeBuffer(); +}; + +/** + * Procesa las variantes de los productos + * @param {Array} productos Lista de productos + * @returns {Array} Lista de variantes procesadas + */ +function procesarVariantes(productos) { + return productos.flatMap((producto) => { + const variantes = producto.variantes_opciones.split(' | ').map((variante) => { + const [datosVariante] = variante.split(','); + const [nombreVariante, descripcionVariante] = datosVariante.split('-'); + return { + idProducto: producto.idProducto, + nombreProducto: producto.nombreProducto, + nombreVariante, + descripcionVariante, + }; + }); + return variantes; + }); +} + +/** + * Procesa las opciones de los productos + * @param {Array} productos Lista de productos + * @returns {Array} Lista de opciones procesadas + */ +function procesarOpciones(productos) { + return productos.flatMap((producto) => { + return producto.variantes_opciones.split(' | ').flatMap((variante) => { + const [datosVariante, ...opcionesParts] = variante.split(','); + const [nombreVariante] = datosVariante.split('-'); + const opcionesCompletas = opcionesParts.join(',').trim(); + + if (!opcionesCompletas) return []; + + return opcionesCompletas + .split(', ') + .filter((opcion) => opcion.trim()) + .map((opcion) => { + const [valorOpcion, skuComercial, skuAutomatico, cantidad] = opcion + .split(':') + .map((valores) => valores.trim()); + + return { + idProducto: producto.idProducto, + nombreProducto: producto.nombreProducto, + nombreVariante, + valorOpcion, + skuComercial, + skuAutomatico, + cantidad, + }; + }); + }); + }); +} From 61fed111c09259a1a50840fa71aba352482f0729 Mon Sep 17 00:00:00 2001 From: NicoH00d Date: Thu, 5 Jun 2025 21:03:27 -0600 Subject: [PATCH 075/116] fix: validacion de proveedor existente --- .../importarProductos.controller.js | 9 +++++++++ .../Repositorios/repositorioValidarProveedor.js | 16 ++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 Productos/Datos/Repositorios/repositorioValidarProveedor.js diff --git a/Productos/Controladores/importarProductos.controller.js b/Productos/Controladores/importarProductos.controller.js index 3fac5842..80393a87 100644 --- a/Productos/Controladores/importarProductos.controller.js +++ b/Productos/Controladores/importarProductos.controller.js @@ -6,6 +6,8 @@ const repositorioCrearOpcion = require('@altertex/pro/repos/repositorioCrearOpci const db = require('@altertex/util/bd/db'); const validarProductoImportado = require('@altertex/util/vali/validarProductoImportado'); const { crearGeneradorSKUConsecutivo } = require('@altertex/util/inter/generarSKUAuto'); +const { proveedorExiste } = require('@altertex/pro/repos/repositorioValidarProveedor'); + /** * Importa productos y sus variantes/opciones para un cliente. @@ -58,6 +60,13 @@ exports.importarProductos = async (req, res) => { errores.push({ fila, error: errorProducto.error }); continue; } + if (producto.idProveedor !== null) { + const existe = await proveedorExiste(conexion, producto.idProveedor); + if (!existe) { + errores.push({ fila, error: `idProveedor ${producto.idProveedor} no existe en la base de datos.` }); + continue; + } + } if (!Array.isArray(variantes) || variantes.length === 0) { errores.push({ fila, error: 'Producto sin variantes válidas.' }); diff --git a/Productos/Datos/Repositorios/repositorioValidarProveedor.js b/Productos/Datos/Repositorios/repositorioValidarProveedor.js new file mode 100644 index 00000000..2f7c1da3 --- /dev/null +++ b/Productos/Datos/Repositorios/repositorioValidarProveedor.js @@ -0,0 +1,16 @@ +/** + * Verifica si un proveedor existe en la base de datos. + * @param {object} conexion - La conexión a la base de datos. + * @param {number|null} idProveedor - El ID del proveedor a verificar. + * @returns {Promise} True si el proveedor existe o si idProveedor es null. + */ +async function proveedorExiste(conexion, idProveedor) { + if (idProveedor === null) return true; // Permitido por diseño + const [result] = await conexion.query( + 'SELECT idProveedor FROM proveedor WHERE idProveedor = ? LIMIT 1', + [idProveedor] + ); + return result.length > 0; +} + +module.exports = { proveedorExiste }; From b5dcd00c67c760a1cd39ff0a0f9c8829fd97c653 Mon Sep 17 00:00:00 2001 From: max Date: Thu, 5 Jun 2025 21:22:33 -0600 Subject: [PATCH 076/116] feat(usuarios): add password hashing for user updates in actualizarUsuario function --- Usuarios/Controladores/actualizarUsuario.controller.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Usuarios/Controladores/actualizarUsuario.controller.js b/Usuarios/Controladores/actualizarUsuario.controller.js index b071e318..16a60a44 100644 --- a/Usuarios/Controladores/actualizarUsuario.controller.js +++ b/Usuarios/Controladores/actualizarUsuario.controller.js @@ -28,6 +28,8 @@ exports.actualizarUsuario = async (req, res) => { } else if (req.body.cambios) { // Si la información viene en el formato esperado (hay cambios) datos = Array.isArray(req.body.cambios) ? req.body.cambios : [req.body.cambios]; + const contraseniaEncriptada = await bcrypt.hash(datos[0]['contrasenia'], 10); + datos[0]['contrasenia'] = contraseniaEncriptada; } else { return res .status(MENSAJES.ERROR_ACTUALIZAR_USUARIO.codigo) From 97766b7dd968ba2c9025e1454360cd505f0c9e62 Mon Sep 17 00:00:00 2001 From: NicoH00d Date: Thu, 5 Jun 2025 21:42:32 -0600 Subject: [PATCH 077/116] =?UTF-8?q?feat:=20agregar=20validaci=C3=B3n=20de?= =?UTF-8?q?=20proveedor=20existente?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Productos/Datos/Repositorios/repositorioValidarProveedor.js | 5 ++++- Utilidades/Constantes/consultasProveedores.js | 6 +++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Productos/Datos/Repositorios/repositorioValidarProveedor.js b/Productos/Datos/Repositorios/repositorioValidarProveedor.js index 2f7c1da3..7886411c 100644 --- a/Productos/Datos/Repositorios/repositorioValidarProveedor.js +++ b/Productos/Datos/Repositorios/repositorioValidarProveedor.js @@ -1,3 +1,6 @@ +const consultas = require('@altertex/util/const/consultasProveedores'); + + /** * Verifica si un proveedor existe en la base de datos. * @param {object} conexion - La conexión a la base de datos. @@ -7,7 +10,7 @@ async function proveedorExiste(conexion, idProveedor) { if (idProveedor === null) return true; // Permitido por diseño const [result] = await conexion.query( - 'SELECT idProveedor FROM proveedor WHERE idProveedor = ? LIMIT 1', + consultas.VERIFICAR_EXISTE, [idProveedor] ); return result.length > 0; diff --git a/Utilidades/Constantes/consultasProveedores.js b/Utilidades/Constantes/consultasProveedores.js index 73fad2c5..95edd293 100644 --- a/Utilidades/Constantes/consultasProveedores.js +++ b/Utilidades/Constantes/consultasProveedores.js @@ -7,4 +7,8 @@ module.exports = { INSERT INTO proveedor (idCliente, nombre, nombreCompania, telefonoContacto, direccion, codigoPostal, pais, estado) VALUES (?, ?, ?, ?, ?, ?, ?, ?); `, -}; + VERIFICAR_EXISTE:` + SELECT idProveedor FROM proveedor WHERE idProveedor = ? LIMIT 1 + ` +} + From dea5ce3802e355a038a79785f6123fa3c4fabff3 Mon Sep 17 00:00:00 2001 From: Hiram <147564077+Hiram10tec@users.noreply.github.com> Date: Thu, 5 Jun 2025 23:20:05 -0600 Subject: [PATCH 078/116] =?UTF-8?q?A=C3=B1adir=20dependencias?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 9 +++++++++ package.json | 1 + 2 files changed, 10 insertions(+) diff --git a/package-lock.json b/package-lock.json index 718dd54f..568fd2e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "exceljs": "^4.4.0", "express": "^4.21.2", "helmet": "^8.1.0", + "i": "^0.3.7", "json2csv": "^6.0.0-alpha.2", "jsonwebtoken": "^9.0.2", "module-alias": "^2.2.3", @@ -7763,6 +7764,14 @@ "node": ">=10.17.0" } }, + "node_modules/i": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/i/-/i-0.3.7.tgz", + "integrity": "sha512-FYz4wlXgkQwIPqhzC5TdNMLSE5+GS1IIDJZY/1ZiEPCT2S3COUVZeT5OW4BmW4r5LHLQuOosSwsvnroG9GR59Q==", + "engines": { + "node": ">=0.4" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", diff --git a/package.json b/package.json index 68d15d58..c205e6d4 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "exceljs": "^4.4.0", "express": "^4.21.2", "helmet": "^8.1.0", + "i": "^0.3.7", "json2csv": "^6.0.0-alpha.2", "jsonwebtoken": "^9.0.2", "module-alias": "^2.2.3", From 37071e04a84bef3dca0b3351d521f9f3b5ed7531 Mon Sep 17 00:00:00 2001 From: NicoH00d Date: Fri, 6 Jun 2025 00:40:36 -0600 Subject: [PATCH 079/116] fix: mensajes de validacion --- .../Validaciones/validarProductoImportado.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Utilidades/Intermediarios/Validaciones/validarProductoImportado.js b/Utilidades/Intermediarios/Validaciones/validarProductoImportado.js index 791ab927..1fd8e2b3 100644 --- a/Utilidades/Intermediarios/Validaciones/validarProductoImportado.js +++ b/Utilidades/Intermediarios/Validaciones/validarProductoImportado.js @@ -59,7 +59,7 @@ module.exports = (producto) => { if ( !producto.nombreComun - || typeof producto.nombreComun !== 'string' + || typeof producto.nombreComun !== 'string' || producto.nombreComun.length > 100 ) { return { @@ -90,14 +90,14 @@ module.exports = (producto) => { producto.marca !== null && (typeof producto.marca !== 'string' || producto.marca.length > 100 || producto.marca.trim() === '') ) { - return { error: 'marca debe ser una cadena de texto o NULL y no exceder 100 caracteres.' }; + return { error: 'marca debe ser una cadena de texto y no exceder 100 caracteres.' }; } if ( producto.modelo !== null && (typeof producto.modelo !== 'string' || producto.modelo.length > 100 || producto.modelo.trim() === '') ) { - return { error: 'modelo debe ser una cadena de texto o NULL y no exceder 100 caracteres.' }; + return { error: 'modelo debe ser una cadena de texto y no exceder 100 caracteres.' }; } if ( @@ -105,7 +105,7 @@ module.exports = (producto) => { && (typeof producto.tipoProducto !== 'string' || producto.tipoProducto.length > 50 || producto.tipoProducto.trim() === '') ) { return { - error: 'tipoProducto debe ser una cadena de texto o NULL y no exceder 50 caracteres.', + error: 'tipoProducto debe ser una cadena de texto y no exceder 50 caracteres.', }; } From 28348632c7ab7db7ceb6b9d79778a445a18f48fb Mon Sep 17 00:00:00 2001 From: NicoH00d Date: Fri, 6 Jun 2025 00:49:58 -0600 Subject: [PATCH 080/116] FIX: mensajes de validacion --- Utilidades/Intermediarios/Validaciones/validarVariante.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Utilidades/Intermediarios/Validaciones/validarVariante.js b/Utilidades/Intermediarios/Validaciones/validarVariante.js index b8646350..e5de5951 100644 --- a/Utilidades/Intermediarios/Validaciones/validarVariante.js +++ b/Utilidades/Intermediarios/Validaciones/validarVariante.js @@ -36,7 +36,7 @@ module.exports = (variante) => { && (typeof variante.descripcion !== 'string' || variante.descripcion.length > 1000) ) { return { - error: 'descripcion debe ser una cadena de texto o NULL y no exceder 1000 caracteres.', + error: 'descripcion debe ser una cadena de texto y no exceder 1000 caracteres.', }; } From 1bb390c3a8a6852d03f0072b8e340b87c680ddc3 Mon Sep 17 00:00:00 2001 From: NicoH00d Date: Fri, 6 Jun 2025 02:24:46 -0600 Subject: [PATCH 081/116] fix: validacion de decripcion repositorio --- .../importarProductos.controller.js | 2 +- .../Validaciones/validarVarianteImportar.js | 44 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 Utilidades/Intermediarios/Validaciones/validarVarianteImportar.js diff --git a/Productos/Controladores/importarProductos.controller.js b/Productos/Controladores/importarProductos.controller.js index 80393a87..99f7f858 100644 --- a/Productos/Controladores/importarProductos.controller.js +++ b/Productos/Controladores/importarProductos.controller.js @@ -1,4 +1,4 @@ -const validarVariante = require('@altertex/util/vali/validarVariante'); +const validarVariante = require('@altertex/util/vali/validarVarianteImportar'); const validarOpcionesImportar = require('@altertex/util/vali/validarOpcionesImportar'); const repositorioCrearProducto = require('@altertex/pro/repos/repositorioCrearProducto'); const repositorioCrearVariante = require('@altertex/pro/repos/repositorioCrearVariante'); diff --git a/Utilidades/Intermediarios/Validaciones/validarVarianteImportar.js b/Utilidades/Intermediarios/Validaciones/validarVarianteImportar.js new file mode 100644 index 00000000..15c0b21e --- /dev/null +++ b/Utilidades/Intermediarios/Validaciones/validarVarianteImportar.js @@ -0,0 +1,44 @@ +//RF26 Crea Producto - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF26 +/** + * Valida los campos de una variante. + * + * Esta función valida los diferentes campos de un objeto `variante` asegurándose de que cumplan con los tipos y restricciones especificadas. Si algún campo no cumple con las condiciones, se devuelve un objeto de error con un mensaje específico. Si todos los campos son válidos, se retorna `null`. + * + * @param {object} variante - El objeto que representa una variante a validar. + * @param {string} variante.nombreVariante - El nombre de la variante, debe ser una cadena de texto de máximo 100 caracteres. + * @param {string|null} variante.descripcion - La descripción de la variante, debe ser una cadena de texto de máximo 1000 caracteres o `null`. + * + * @returns {object|null} Un objeto con una propiedad `error` si hay un error de validación, o `null` si todos los campos son válidos. + * @example + * const variante = { + * nombreVariante: 'Tamaño L', + * descripcion: 'Variante para talla grande', + * }; + * + * const resultado = validarVariante(variante); + * console.log(resultado); // null si todo está bien, o un objeto de error si algo es inválido + */ +// prettier-ignore +module.exports = (variante) => { + if ( + !variante.nombreVariante + || typeof variante.nombreVariante !== 'string' + || variante.nombreVariante.length > 100 + ) { + return { + error: + 'nombreVariante es requerido, debe ser una cadena de texto y no exceder 100 caracteres.', + }; + } + + if ( + variante.descripcion !== null + && (typeof variante.descripcion !== 'string' || variante.descripcion.length > 1000 || variante.descripcion.trim() === '') + ) { + return { + error: 'descripcion debe ser una cadena de texto y no exceder 1000 caracteres.', + }; + } + + return null; +}; From ba297a8160ae821848cfd19c6f4eee1e026d65e3 Mon Sep 17 00:00:00 2001 From: Diego Alfaro Pinto Date: Fri, 6 Jun 2025 03:04:42 -0600 Subject: [PATCH 082/116] feat: agregar proteccion CSRF --- app.js | 31 ++++++++++--- package-lock.json | 113 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 3 files changed, 139 insertions(+), 6 deletions(-) diff --git a/app.js b/app.js index 1ad91006..c2ded97d 100644 --- a/app.js +++ b/app.js @@ -6,6 +6,7 @@ const express = require('express'); const cors = require('cors'); const cookieParser = require('cookie-parser'); const swaggerJSDoc = require('swagger-jsdoc'); +const csrf = require('csurf') //Importaciones de configuracion const corsOptions = require('@altertex/config/corsOptions'); @@ -35,10 +36,17 @@ const puerto = process.env.PORT || 5000; //Configuracion de aplicacion express const app = express(); -app.use(express.json({ limit: '5mb' })); -app.use(express.urlencoded({ limit: '5mb', extended: true })); +app.use(express.json({limit: '5mb'})); +app.use(express.urlencoded({limit: '5mb', extended: true})); app.use(cookieParser()); app.use(cors(corsOptions)); +const proteccionCsrf = csrf({ + cookie: { + httpOnly: true, + secure: false, + sameSite: 'strict' + } +}) cronCuotas.start(); @@ -57,14 +65,25 @@ app.use(RUTAS.API, rutasPedidos); app.use(RUTAS.API, rutasEventos); app.use(RUTAS.API, rutasPagos); +app.use((req, res, next) => { + if (req.method === 'GET' && req.path === '/api/csrf-token') { + return next() + } + proteccionCsrf(req, res, next) +}) + +app.get('/api/csrf-token', proteccionCsrf, (req, res) => { + res.json({csrfToken: req.csrfToken()}); +}); + app.get('/', async (req, res) => { - return res - .status(200) - .json({ mensaje: `Ruta por default Proyecto Text&Lines en ambiente: ${process.env.NODE_ENV}` }); + return res + .status(200) + .json({mensaje: `Ruta por default Proyecto Text&Lines en ambiente: ${process.env.NODE_ENV}`}); }); //Configuracion de swaggerUI const swaggerSpec = swaggerJSDoc(opcionesSwagger); app.use(RUTAS.API_DOCS, swaggerUI.serve, swaggerUI.setup(swaggerSpec)); app.listen(puerto, () => - console.log(`Servidor corriendo en puerto ${puerto} [${process.env.NODE_ENV}]`)); + console.log(`Servidor corriendo en puerto ${puerto} [${process.env.NODE_ENV}]`)); diff --git a/package-lock.json b/package-lock.json index 718dd54f..8da91674 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "body-parser": "^1.20.3", "cookie-parser": "^1.4.7", "cors": "^2.8.5", + "csurf": "^1.11.0", "date-fns": "^4.1.0", "dotenv": "^16.4.7", "exceljs": "^4.4.0", @@ -6107,12 +6108,92 @@ "node": ">= 8" } }, + "node_modules/csrf": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/csrf/-/csrf-3.1.0.tgz", + "integrity": "sha512-uTqEnCvWRk042asU6JtapDTcJeeailFy4ydOQS28bj1hcLnYRiqi8SsD2jS412AY1I/4qdOwWZun774iqywf9w==", + "dependencies": { + "rndm": "1.2.0", + "tsscmp": "1.0.6", + "uid-safe": "2.1.5" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/css.escape": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", "license": "MIT" }, + "node_modules/csurf": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/csurf/-/csurf-1.11.0.tgz", + "integrity": "sha512-UCtehyEExKTxgiu8UHdGvHj4tnpE/Qctue03Giq5gPgMQ9cg/ciod5blZQ5a4uCEenNQjxyGuzygLdKUmee/bQ==", + "deprecated": "This package is archived and no longer maintained. For support, visit https://github.com/expressjs/express/discussions", + "dependencies": { + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "csrf": "3.1.0", + "http-errors": "~1.7.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/csurf/node_modules/cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/csurf/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/csurf/node_modules/http-errors": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", + "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/csurf/node_modules/setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "node_modules/csurf/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/csurf/node_modules/toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "engines": { + "node": ">=0.6" + } + }, "node_modules/culvert": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/culvert/-/culvert-0.1.2.tgz", @@ -11101,6 +11182,14 @@ "node": ">=4" } }, + "node_modules/random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -11582,6 +11671,11 @@ "rimraf": "bin.js" } }, + "node_modules/rndm": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/rndm/-/rndm-1.2.0.tgz", + "integrity": "sha512-fJhQQI5tLrQvYIYFpOnFinzv9dwmR7hRnUz1XqP3OJ1jIweTNOd6aTO4jwQSgcBSFUB+/KHJxuGneime+FdzOw==" + }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", @@ -12797,6 +12891,14 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/tsscmp": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", + "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==", + "engines": { + "node": ">=0.6.x" + } + }, "node_modules/tv4": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/tv4/-/tv4-1.3.0.tgz", @@ -12889,6 +12991,17 @@ "ts-toolbelt": "^9.6.0" } }, + "node_modules/uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "dependencies": { + "random-bytes": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", diff --git a/package.json b/package.json index 68d15d58..fbaca863 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "body-parser": "^1.20.3", "cookie-parser": "^1.4.7", "cors": "^2.8.5", + "csurf": "^1.11.0", "date-fns": "^4.1.0", "dotenv": "^16.4.7", "exceljs": "^4.4.0", From 28d5097cae5026578d8fd6246d84c984a242ea40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valeria=20Zu=C3=B1iga=20Mendoza?= Date: Fri, 6 Jun 2025 05:24:12 -0600 Subject: [PATCH 083/116] feat: actualizar sets productos --- .../actualizarSetsProductos.controller.js | 68 +++++++++++-------- .../repositorioActualizarSetsProductos.js | 63 +++++++++++------ .../actualizarSetsProductos.routes.js | 2 +- .../Constantes/consultasSetsProductos.js | 23 ++++++- .../Constantes/mensajesSetsProductos.js | 20 ++++++ 5 files changed, 124 insertions(+), 52 deletions(-) diff --git a/SetsProductos/Controladores/actualizarSetsProductos.controller.js b/SetsProductos/Controladores/actualizarSetsProductos.controller.js index ccf2e14a..df92fc93 100644 --- a/SetsProductos/Controladores/actualizarSetsProductos.controller.js +++ b/SetsProductos/Controladores/actualizarSetsProductos.controller.js @@ -3,37 +3,51 @@ const repositorio = require('@altertex/setspro/repos/repositorioActualizarSetsPr //RF[44] Actualizar set de productos - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF44] /** - * Controlador para actualizar la información de un ... + * Controlador HTTP para actualizar un set de productos. + * + * Valida que el cuerpo de la solicitud contenga los datos necesarios, + * y delega la lógica de actualización al repositorio correspondiente. + * + * Respuestas posibles: + * - 400 si faltan datos en el cuerpo de la solicitud. + * - 200 si el set se actualiza correctamente. + * - 500 si ocurre un error en el proceso de actualización. + * + * @async + * @function actualizarSetProductos + * @param {Express.Request} req - Objeto de solicitud HTTP de Express. + * @param {Express.Response} res - Objeto de respuesta HTTP de Express. + * @returns {Promise} La respuesta HTTP con el estado y mensaje correspondiente. */ -exports.actualizarSetProducto = async (req, res) => { - let datos; +exports.actualizarSetProductos = async (req, res) => { + const datosActualizacion = req.body; - // Si no hay cambios - if (req.body.id || req.body.idSetProducto) { - datos = [req.body]; - } else if (req.body.cambios) { - // Si la información viene en el formato esperado (hay cambios) - datos = Array.isArray(req.body.cambios) ? req.body.cambios : [req.body.cambios]; - } else { - return res - .status(MENSAJES.ERROR_ACTUALIZAR_SET_PRODUCTOS.codigo) - .json({ mensaje: MENSAJES.ERROR_ACTUALIZAR_SET_PRODUCTOS.mensaje }); - } - - if (!datos || datos.length === 0) { - return res - .status(MENSAJES.ERROR_ACTUALIZAR_SET_PRODUCTOS.codigo) - .json({ mensaje: MENSAJES.ERROR_ACTUALIZAR_SET_PRODUCTOS.mensaje }); + // Validación básica de datos requeridos + if ( + !datosActualizacion || + !datosActualizacion.nombre || + !datosActualizacion.productos || + !Array.isArray(datosActualizacion.productos) + ) { + return res.status(MENSAJES.FORMATO_INVALIDO_DATOS.codigo).json({ + mensaje: MENSAJES.FORMATO_INVALIDO_DATOS.mensaje, + detalles: 'Se requieren nombre y lista de productos', + }); } try { - await repositorio.actualizarSetProducto(datos); - return res - .status(MENSAJES.SET_PRODUCTOS_ACTUALIZADO.codigo) - .json({ mensaje: MENSAJES.SET_PRODUCTOS_ACTUALIZADO.mensaje, datos }); - } catch { - return res - .status(MENSAJES.ERROR_ACTUALIZAR_SET_PRODUCTOS.codigo) - .json({ mensaje: MENSAJES.ERROR_ACTUALIZAR_SET_PRODUCTOS.mensaje }); + await repositorio.actualizarSetProductos(datosActualizacion); + + return res.status(MENSAJES.SET_ACTUALIZADO.codigo).json({ + mensaje: MENSAJES.SET_ACTUALIZADO.mensaje, + datos: datosActualizacion, + }); + } catch (error) { + console.error('Error al actualizar set de productos:', error); + + return res.status(MENSAJES.ERROR_ACTUALIZAR_SET.codigo).json({ + mensaje: MENSAJES.ERROR_ACTUALIZAR_SET.mensaje, + error: error.message, + }); } }; diff --git a/SetsProductos/Datos/Repositorios/repositorioActualizarSetsProductos.js b/SetsProductos/Datos/Repositorios/repositorioActualizarSetsProductos.js index d97c758e..a213f285 100644 --- a/SetsProductos/Datos/Repositorios/repositorioActualizarSetsProductos.js +++ b/SetsProductos/Datos/Repositorios/repositorioActualizarSetsProductos.js @@ -1,31 +1,52 @@ const correrQuery = require('@altertex/util/ser/correrQuery'); const MENSAJES = require('@altertex/util/const/mensajesSetsProductos'); -const CONSULTAS_SETS_PRODUCTOS = require('@altertex/util/const/consultasSetsProductos'); +const CONSULTAS = require('@altertex/util/const/consultasSetsProductos'); -//RF[44] Actualizar set de productos - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF44] +// RF[44] Actualizar set de productos - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF44] /** - *Repositorio para ... + * Actualiza un set de productos con su información general y productos asociados. * + * @param {object} datos - Datos para actualizar el set. + * @param {number} datos.idSetProducto - ID del set de productos. + * @param {number} datos.idCliente - ID del cliente. + * @param {string} datos.nombre - Nombre interno. + * @param {string} datos.descripcion - Descripción. + * @param {boolean} datos.activo - Estado activo/inactivo. + * @param {number[]} datos.productos - Lista de IDs de productos a asociar. Array vacío elimina todas las asociaciones. */ -exports.actualizarSetProducto = async (datos) => { - if (!Array.isArray(datos) || datos.length === 0) { - throw new Error('Sin datos para actualizar.'); - } +exports.actualizarSetProductos = async (datos) => { + const { idSetProducto, idCliente, nombre, descripcion, activo, productos } = datos; + try { - await Promise.all( - datos.map(({ idSetProducto, idCliente, nombre, nombreVisible, descripcion, activo }) => { - return correrQuery(CONSULTAS_SETS_PRODUCTOS.ACTUALIZAR, [ - idCliente, - nombre, - nombreVisible, - descripcion, - activo, - idSetProducto, - ]); - }) - ); - } catch { - throw new Error(MENSAJES.ERROR_ACTUALIZAR_SET_PRODUCTOS.mensaje); + // 1. Actualizar info básica del set + await correrQuery(CONSULTAS.ACTUALIZAR_SET_INFO, [nombre, descripcion, activo, idSetProducto]); + + // 2. Manejar productos asociados al set + if (Array.isArray(productos)) { + if (productos.length > 0) { + // Verificar que los productos pertenezcan al cliente + const productosSTR = productos.join(', '); + const valores = productos + .map((idProducto) => `(${idSetProducto}, ${idProducto})`) + .join(', '); + + // Eliminar asociaciones actuales que no estén en la nueva lista + await correrQuery( + CONSULTAS.ELIMINAR_PRODUCTOS_DEL_SET.replace('__ID__', idSetProducto).replace( + '__PRODUCTOS__', + productosSTR + ) + ); + + // Agregar nuevos productos al set + await correrQuery(CONSULTAS.AGREGAR_PRODUCTOS_AL_SET.replace('__VALORES__', valores)); + } else { + // Eliminar todas las asociaciones si el array está vacío + await correrQuery(CONSULTAS.ELIMINAR_TODOS_PRODUCTOS_DEL_SET, [idSetProducto]); + } + } + } catch (error) { + throw new Error(error.message || MENSAJES.ERROR_ACTUALIZAR_SET_PRODUCTOS.mensaje); } }; diff --git a/SetsProductos/Rutas/RutasIndividuales/actualizarSetsProductos.routes.js b/SetsProductos/Rutas/RutasIndividuales/actualizarSetsProductos.routes.js index 1a1a7e31..d2f5892b 100644 --- a/SetsProductos/Rutas/RutasIndividuales/actualizarSetsProductos.routes.js +++ b/SetsProductos/Rutas/RutasIndividuales/actualizarSetsProductos.routes.js @@ -23,7 +23,7 @@ ruteador.put( autorizarToken, limitePeticionesDiarias, revisarPermisos(PERMISOS.ACTUALIZAR_SET_PRODUCTOS), - controlador.actualizarSetProducto + controlador.actualizarSetProductos ); module.exports = ruteador; diff --git a/Utilidades/Constantes/consultasSetsProductos.js b/Utilidades/Constantes/consultasSetsProductos.js index f9a4a84a..4d364d6e 100644 --- a/Utilidades/Constantes/consultasSetsProductos.js +++ b/Utilidades/Constantes/consultasSetsProductos.js @@ -48,7 +48,24 @@ module.exports = { FROM producto WHERE idProducto IN (__IDS__); `, - ACTUALIZAR: ` - UPDATE set_producto SET idCliente = ?, nombre = ?, nombreVisible = ?, descripcion = ?, activo = ? WHERE idSetProducto = ?; - `, + ACTUALIZAR_SET_INFO: ` + UPDATE set_producto + SET nombre = ?, descripcion = ?, activo = ? + WHERE idSetProducto = ? + `, + + ELIMINAR_PRODUCTOS_DEL_SET: ` + DELETE FROM producto_set_producto + WHERE idSetProducto = __ID__ AND idProducto NOT IN (__PRODUCTOS__) + `, + + AGREGAR_PRODUCTOS_AL_SET: ` + INSERT IGNORE INTO producto_set_producto (idSetProducto, idProducto) + VALUES __VALORES__ + `, + + ELIMINAR_TODOS_PRODUCTOS_DEL_SET: ` + DELETE FROM producto_set_producto + WHERE idSetProducto = ? + `, }; diff --git a/Utilidades/Constantes/mensajesSetsProductos.js b/Utilidades/Constantes/mensajesSetsProductos.js index ea751168..0d832da9 100644 --- a/Utilidades/Constantes/mensajesSetsProductos.js +++ b/Utilidades/Constantes/mensajesSetsProductos.js @@ -64,4 +64,24 @@ module.exports = { codigo: 500, mensaje: 'Ocurrió un error al actualizar el set de productos', }, + FORMATO_INVALIDO_DATOS: { + codigo: 400, + mensaje: 'Formato de datos inválido para actualizar el set de productos', + }, + SET_ACTUALIZADO: { + codigo: 200, + mensaje: 'Set de productos actualizado correctamente', + }, + ERROR_ACTUALIZAR_SET: { + codigo: 500, + mensaje: 'Error interno al actualizar el set de productos', + }, + SET_NO_ENCONTRADO: { + codigo: 404, + mensaje: 'El set de productos no fue encontrado', + }, + PRODUCTOS_INVALIDOS: { + codigo: 400, + mensaje: 'La lista de productos contiene elementos inválidos', + }, }; From 88606d99402847d8524d520db7f39aa0e27e5080 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valeria=20Zu=C3=B1iga=20Mendoza?= Date: Fri, 6 Jun 2025 06:14:45 -0600 Subject: [PATCH 084/116] feat: Desplegar los productos iniciales para actualizar set --- .../Datos/Repositorios/repositorioActualizarSetsProductos.js | 2 +- .../Rutas/RutasIndividuales/consultarSetsProductos.routes.js | 1 - Utilidades/Constantes/consultasSetsProductos.js | 5 +++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/SetsProductos/Datos/Repositorios/repositorioActualizarSetsProductos.js b/SetsProductos/Datos/Repositorios/repositorioActualizarSetsProductos.js index a213f285..51577777 100644 --- a/SetsProductos/Datos/Repositorios/repositorioActualizarSetsProductos.js +++ b/SetsProductos/Datos/Repositorios/repositorioActualizarSetsProductos.js @@ -16,7 +16,7 @@ const CONSULTAS = require('@altertex/util/const/consultasSetsProductos'); * @param {number[]} datos.productos - Lista de IDs de productos a asociar. Array vacío elimina todas las asociaciones. */ exports.actualizarSetProductos = async (datos) => { - const { idSetProducto, idCliente, nombre, descripcion, activo, productos } = datos; + const { idSetProducto, nombre, descripcion, activo, productos } = datos; try { // 1. Actualizar info básica del set diff --git a/SetsProductos/Rutas/RutasIndividuales/consultarSetsProductos.routes.js b/SetsProductos/Rutas/RutasIndividuales/consultarSetsProductos.routes.js index 8ba2d771..2e7299fb 100644 --- a/SetsProductos/Rutas/RutasIndividuales/consultarSetsProductos.routes.js +++ b/SetsProductos/Rutas/RutasIndividuales/consultarSetsProductos.routes.js @@ -6,7 +6,6 @@ const autorizarToken = require('@altertex/util/inter/autorizarToken'); const verificarPermisos = require('@altertex/util/inter/verificarPermisos'); const limitePeticionesDiarias = require('@altertex/util/inter/limitePeticiones'); - const PERMISOS = require('@altertex/util/const/permisos'); const RUTAS = require('@altertex/util/const/rutas'); diff --git a/Utilidades/Constantes/consultasSetsProductos.js b/Utilidades/Constantes/consultasSetsProductos.js index 4d364d6e..adf15222 100644 --- a/Utilidades/Constantes/consultasSetsProductos.js +++ b/Utilidades/Constantes/consultasSetsProductos.js @@ -5,7 +5,8 @@ module.exports = { sp.descripcion, sp.activo, GROUP_CONCAT(DISTINCT p.nombreComun SEPARATOR ', ') AS productos, - GROUP_CONCAT(DISTINCT ge.nombre SEPARATOR ', ') AS grupos + GROUP_CONCAT(DISTINCT ge.nombre SEPARATOR ', ') AS grupos, + GROUP_CONCAT(p.idProducto SEPARATOR ', ') AS idsProductos FROM set_producto sp LEFT JOIN producto_set_producto psp ON psp.idSetProducto = sp.idSetProducto LEFT JOIN producto p ON p.idProducto = psp.idProducto @@ -50,7 +51,7 @@ module.exports = { `, ACTUALIZAR_SET_INFO: ` UPDATE set_producto - SET nombre = ?, descripcion = ?, activo = ? + SET nombre = ?, activo = ?, descripcion = ? WHERE idSetProducto = ? `, From 349aed4ed3c1797cb9a4a3341e90cfe629969866 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valeria=20Zu=C3=B1iga=20Mendoza?= Date: Fri, 6 Jun 2025 06:46:03 -0600 Subject: [PATCH 085/116] fix: Corregir errores de lint --- .../Controladores/actualizarSetsProductos.controller.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SetsProductos/Controladores/actualizarSetsProductos.controller.js b/SetsProductos/Controladores/actualizarSetsProductos.controller.js index df92fc93..3ad09efe 100644 --- a/SetsProductos/Controladores/actualizarSetsProductos.controller.js +++ b/SetsProductos/Controladores/actualizarSetsProductos.controller.js @@ -24,10 +24,10 @@ exports.actualizarSetProductos = async (req, res) => { // Validación básica de datos requeridos if ( - !datosActualizacion || - !datosActualizacion.nombre || - !datosActualizacion.productos || - !Array.isArray(datosActualizacion.productos) + !datosActualizacion + || !datosActualizacion.nombre + || !datosActualizacion.productos + || !Array.isArray(datosActualizacion.productos) ) { return res.status(MENSAJES.FORMATO_INVALIDO_DATOS.codigo).json({ mensaje: MENSAJES.FORMATO_INVALIDO_DATOS.mensaje, From 02edcd33e813b841731a5e1b416bde443eb5722c Mon Sep 17 00:00:00 2001 From: ArturoSanRod Date: Fri, 6 Jun 2025 07:33:14 -0600 Subject: [PATCH 086/116] =?UTF-8?q?Implementaci=C3=B3n=20semi=20funcional?= =?UTF-8?q?=20de=20actualizar=20set=20de=20cuotas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../actualizarSetCuotas.controller.js | 19 +++++++ .../actualizarSetCuotasRepositorio.js | 49 +++++++++++++++++++ .../actualizarSetCuotas.routes.js | 19 +++++++ Cuotas/Rutas/indexCuotas.routes.js | 4 ++ Utilidades/Constantes/consultasCuotas.js | 39 ++++++++++----- Utilidades/Constantes/mensajesCuotas.js | 11 +++++ Utilidades/Constantes/rutas.js | 1 + 7 files changed, 131 insertions(+), 11 deletions(-) create mode 100644 Cuotas/Controladores/actualizarSetCuotas.controller.js create mode 100644 Cuotas/Datos/Repositorios/actualizarSetCuotasRepositorio.js create mode 100644 Cuotas/Rutas/RutasIndividuales/actualizarSetCuotas.routes.js diff --git a/Cuotas/Controladores/actualizarSetCuotas.controller.js b/Cuotas/Controladores/actualizarSetCuotas.controller.js new file mode 100644 index 00000000..72422ec6 --- /dev/null +++ b/Cuotas/Controladores/actualizarSetCuotas.controller.js @@ -0,0 +1,19 @@ +const MENSAJES_CUOTAS = require('@altertex/util/const/mensajesCuotas'); +const repositorio = require('@altertex/cuota/repos/actualizarSetCuotasRepositorio'); + +exports.actualizarSetCuotas = async (req, res) => { + try { + const { idCuotaSet, cambios } = req.body; + + if (!idCuotaSet || !cambios) { + return res.status(400).json({ mensaje: MENSAJES_CUOTAS.PARAMETROS_INVALIDOS.mensaje }); + } + + await repositorio.actualizarSetCuotas(idCuotaSet, cambios); + + return res.status(200).json({ mensaje: MENSAJES_CUOTAS.ACTUALIZACION_EXITOSA.mensaje }); + } catch (error) { + console.error('[ERROR] actualizarSetCuotas:', error); + return res.status(500).json({ mensaje: error.message || MENSAJES_CUOTAS.ERROR_ACTUALIZACION.mensaje }); + } +}; \ No newline at end of file diff --git a/Cuotas/Datos/Repositorios/actualizarSetCuotasRepositorio.js b/Cuotas/Datos/Repositorios/actualizarSetCuotasRepositorio.js new file mode 100644 index 00000000..90692923 --- /dev/null +++ b/Cuotas/Datos/Repositorios/actualizarSetCuotasRepositorio.js @@ -0,0 +1,49 @@ +const db = require('@altertex/util/bd/db'); +const CONSULTAS_CUOTAS = require('@altertex/util/const/consultasCuotas'); +const correrQuery = require('@altertex/util/ser/correrQuery'); +const MENSAJES = require('@altertex/util/const/mensajesCuotas'); + + +exports.actualizarSetCuotas = async (idCuotaSet, cambios) => { + const { + nombre, + descripcion, + periodoRenovacion, + renovacionHabilitada, + productos = [] + } = cambios; + + console.log('[DEBUG] Iniciando actualización del set de cuotas', { idCuotaSet, cambios }); + + // Actualizar cuota_set + const resultado = await correrQuery(CONSULTAS_CUOTAS.ACTUALIZAR_CUOTA_SET, [ + nombre, + descripcion, + periodoRenovacion, + renovacionHabilitada, + new Date(), // ultimaActualizacion + idCuotaSet + ]); + + console.log('[DEBUG] Resultado UPDATE cuota_set:', resultado); + + if (!resultado || resultado.affectedRows === 0) { + throw new Error(MENSAJES.ERROR_ACTUALIZACION.mensaje); + } + + // Eliminar productos anteriores + await correrQuery(CONSULTAS_CUOTAS.ELIMINAR_PRODUCTOS_CUOTA_SET, [idCuotaSet]); + + // Insertar nuevos productos + for (const producto of productos) { + const { idProducto, limite, limiteActual } = producto; + await correrQuery(CONSULTAS_CUOTAS.INSERTAR_CUOTA_PRODUCTO_ACTUALIZAR, [ + idCuotaSet, + idProducto, + limite, + limiteActual + ]); + } + + console.log('[DEBUG] Set de cuotas actualizado exitosamente.'); +}; diff --git a/Cuotas/Rutas/RutasIndividuales/actualizarSetCuotas.routes.js b/Cuotas/Rutas/RutasIndividuales/actualizarSetCuotas.routes.js new file mode 100644 index 00000000..ead29c49 --- /dev/null +++ b/Cuotas/Rutas/RutasIndividuales/actualizarSetCuotas.routes.js @@ -0,0 +1,19 @@ +const express = require('express'); +const controlador = require('@altertex/cuota/ctrl/actualizarSetCuotas.controller'); +const autorizarToken = require('@altertex/util/inter/autorizarToken'); +const revisarApiKey = require('@altertex/util/inter/revisarApiKey'); +const verificarPermisos = require('@altertex/util/inter/verificarPermisos'); +const RUTAS = require('@altertex/util/const/rutas'); +const PERMISOS = require('@altertex/util/const/permisos'); + +const ruteador = express.Router(); + +ruteador.put( + RUTAS.CUOTAS.ACTUALIZAR_SET_CUOTAS, + revisarApiKey(), + autorizarToken, + verificarPermisos(PERMISOS.ACTUALIZAR_SET_CUOTAS), + controlador.actualizarSetCuotas +); + +module.exports = ruteador; diff --git a/Cuotas/Rutas/indexCuotas.routes.js b/Cuotas/Rutas/indexCuotas.routes.js index ea69bba4..0c4056a8 100644 --- a/Cuotas/Rutas/indexCuotas.routes.js +++ b/Cuotas/Rutas/indexCuotas.routes.js @@ -5,6 +5,8 @@ const rutaObtenerOpcionesCuota = require('@altertex/cuota/rutasInd/obtenerOpcion const rutasConsultarListaCuotas = require('@altertex/cuota/rutasInd/consultarCuotas.routes'); const rutaEliminarSetCuotas = require('@altertex/cuota/rutasInd/eliminarSetCuotas.routes'); const rutaLeerCuota = require('@altertex/cuota/rutasInd/leerSetCuotas.routes'); +const rutaActualizarSetCuotas = require('@altertex/cuota/rutasInd/actualizarSetCuotas.routes'); + const RUTAS = require('@altertex/util/const/rutas'); @@ -13,5 +15,7 @@ ruteador.use(RUTAS.CUOTAS.BASE, rutaObtenerOpcionesCuota); ruteador.use(RUTAS.CUOTAS.BASE, rutasConsultarListaCuotas); ruteador.use(RUTAS.CUOTAS.BASE, rutaEliminarSetCuotas); ruteador.use(RUTAS.CUOTAS.BASE, rutaLeerCuota); +ruteador.use(RUTAS.CUOTAS.BASE, rutaActualizarSetCuotas); + module.exports = ruteador; diff --git a/Utilidades/Constantes/consultasCuotas.js b/Utilidades/Constantes/consultasCuotas.js index cc1d4465..3308c3d1 100644 --- a/Utilidades/Constantes/consultasCuotas.js +++ b/Utilidades/Constantes/consultasCuotas.js @@ -52,15 +52,32 @@ module.exports = { WHERE cs.idCuotaSet = ?; `, - LEER_CUOTA_SET_PRODUCTOS: ` - SELECT - p.nombreComun, - csp.limite AS cuota_valor - FROM cuota_set cs - JOIN cuota_set_producto csp - ON cs.idCuotaSet = csp.idCuotaSet - JOIN producto p - ON p.idProducto = csp.idProducto - WHERE cs.idCuotaSet = ?; - `, + LEER_CUOTA_SET_PRODUCTOS: + `SELECT + p.idProducto, + p.nombreComun, + csp.limite AS cuota_valor, + csp.limite_actual + FROM cuota_set cs + JOIN cuota_set_producto csp + ON cs.idCuotaSet = csp.idCuotaSet + JOIN producto p + ON p.idProducto = csp.idProducto + WHERE cs.idCuotaSet = ?`, + + ACTUALIZAR_CUOTA_SET: ` + UPDATE cuota_set + SET nombre = ?, descripcion = ?, periodoRenovacion = ?, renovacionHabilitada = ?, ultimaActualizacion = ? + WHERE idCuotaSet = ?; + `, + + INSERTAR_CUOTA_PRODUCTO_ACTUALIZAR: ` + INSERT INTO cuota_set_producto (idCuotaSet, idProducto, limite, limite_actual) + VALUES (?, ?, ?, ?) + `, + + ELIMINAR_PRODUCTOS_CUOTA_SET: ` + DELETE FROM cuota_set_producto WHERE idCuotaSet = ?; + `, + }; diff --git a/Utilidades/Constantes/mensajesCuotas.js b/Utilidades/Constantes/mensajesCuotas.js index a730325f..9ac28661 100644 --- a/Utilidades/Constantes/mensajesCuotas.js +++ b/Utilidades/Constantes/mensajesCuotas.js @@ -58,4 +58,15 @@ module.exports = { codigo: 500, mensaje: 'Error interno al obtener el set de cuotas.', }, + + ACTUALIZACION_EXITOSA: { + codigo: 200, + mensaje: 'Set de cuotas actualizado correctamente.' + }, + + ERROR_ACTUALIZACION: { + codigo: 500, + mensaje: 'No se pudo actualizar el set de cuotas.' + }, + }; diff --git a/Utilidades/Constantes/rutas.js b/Utilidades/Constantes/rutas.js index fa697b2b..c8a6ce34 100644 --- a/Utilidades/Constantes/rutas.js +++ b/Utilidades/Constantes/rutas.js @@ -83,6 +83,7 @@ module.exports = { CONSULTAR_LISTA: '/consultar-lista', ELIMINAR_SET_CUOTAS: '/eliminar-set-cuotas', LEER_SET_CUOTAS: '/leer-set-cuotas', + ACTUALIZAR_SET_CUOTAS: '/actualizar-set-cuotas', }, ROLES: { BASE: '/roles', From 2a60635454daef4dccf65ccd8221aa8139a05f8b Mon Sep 17 00:00:00 2001 From: ArturoSanRod Date: Fri, 6 Jun 2025 07:49:46 -0600 Subject: [PATCH 087/116] Funcionalidad de actualizar pedido --- .../actualizarPedido.controller.js | 38 +++++++++++++++++++ .../repositorioActualizarPedido.js | 29 ++++++++++++++ .../actualizarPedidos.routes.js | 22 +++++++++++ Pedidos/Rutas/indexPedidos.routes.js | 3 ++ Utilidades/Constantes/consultasPedidos.js | 10 +++++ Utilidades/Constantes/mensajesPedidos.js | 9 +++++ Utilidades/Constantes/rutas.js | 1 + 7 files changed, 112 insertions(+) create mode 100644 Pedidos/Controladores/actualizarPedido.controller.js create mode 100644 Pedidos/Datos/Repositorios/repositorioActualizarPedido.js create mode 100644 Pedidos/Rutas/RutasIndividuales/actualizarPedidos.routes.js diff --git a/Pedidos/Controladores/actualizarPedido.controller.js b/Pedidos/Controladores/actualizarPedido.controller.js new file mode 100644 index 00000000..85c9908c --- /dev/null +++ b/Pedidos/Controladores/actualizarPedido.controller.js @@ -0,0 +1,38 @@ +const MENSAJES = require('@altertex/util/const/mensajesPedidos'); +const repositorio = require('@altertex/pedidos/repos/repositorioActualizarPedido'); + +/** + * RF[62] - Actualizar Pedido + * Controlador para actualizar la información de uno o varios pedidos. + */ +exports.actualizarPedido = async (req, res) => { + let datos; + + if (req.body.idPedido) { + datos = [req.body]; + } else if (req.body.cambios) { + datos = Array.isArray(req.body.cambios) ? req.body.cambios : [req.body.cambios]; + } else { + return res.status(MENSAJES.ERROR_ACTUALIZAR_PEDIDO.codigo).json({ + mensaje: MENSAJES.ERROR_ACTUALIZAR_PEDIDO.mensaje, + }); + } + + if (!datos || datos.length === 0) { + return res.status(MENSAJES.ERROR_ACTUALIZAR_PEDIDO.codigo).json({ + mensaje: MENSAJES.ERROR_ACTUALIZAR_PEDIDO.mensaje, + }); + } + + try { + await repositorio.actualizarPedido(datos); + return res.status(MENSAJES.PEDIDO_ACTUALIZADO.codigo).json({ + mensaje: MENSAJES.PEDIDO_ACTUALIZADO.mensaje, + datos, + }); + } catch { + return res.status(MENSAJES.ERROR_ACTUALIZAR_PEDIDO.codigo).json({ + mensaje: MENSAJES.ERROR_ACTUALIZAR_PEDIDO.mensaje, + }); + } +}; diff --git a/Pedidos/Datos/Repositorios/repositorioActualizarPedido.js b/Pedidos/Datos/Repositorios/repositorioActualizarPedido.js new file mode 100644 index 00000000..e12b9445 --- /dev/null +++ b/Pedidos/Datos/Repositorios/repositorioActualizarPedido.js @@ -0,0 +1,29 @@ +const correrQuery = require('@altertex/util/ser/correrQuery'); +const MENSAJES = require('@altertex/util/const/mensajesPedidos'); +const CONSULTAS = require('@altertex/util/const/consultasPedidos'); + +/** + * RF[62] - Actualizar Pedido + * Repositorio para actualizar los datos de uno o varios pedidos. + */ +exports.actualizarPedido = async (datos) => { + if (!Array.isArray(datos) || datos.length === 0) { + throw new Error('Sin datos para actualizar.'); + } + + try { + await Promise.all( + datos.map(({ idPedido, estado, precioTotal, idEnvio, idPago }) => { + return correrQuery(CONSULTAS.ACTUALIZAR_PEDIDO, [ + estado, + precioTotal, + idEnvio, + idPago, + idPedido, + ]); + }) + ); + } catch { + throw new Error(MENSAJES.ERROR_ACTUALIZAR_PEDIDO.mensaje); + } +}; diff --git a/Pedidos/Rutas/RutasIndividuales/actualizarPedidos.routes.js b/Pedidos/Rutas/RutasIndividuales/actualizarPedidos.routes.js new file mode 100644 index 00000000..8d2d2eb5 --- /dev/null +++ b/Pedidos/Rutas/RutasIndividuales/actualizarPedidos.routes.js @@ -0,0 +1,22 @@ +const express = require('express'); +const ruteador = express.Router(); + +const controlador = require('@altertex/pedidos/ctrl/actualizarPedido.controller'); +const revisarApiKey = require('@altertex/util/inter/revisarApiKey'); +const autorizarToken = require('@altertex/util/inter/autorizarToken'); +const verificarPermisos = require('@altertex/util/inter/verificarPermisos'); +const limitePeticiones = require('@altertex/util/inter/limitePeticiones'); +const RUTAS = require('@altertex/util/const/rutas'); +const PERMISOS = require('@altertex/util/const/permisos'); + +// RF[62] - Actualizar Pedido +ruteador.put( + RUTAS.PEDIDOS.ACTUALIZAR_PEDIDO, + revisarApiKey(), + autorizarToken, + limitePeticiones, + verificarPermisos(PERMISOS.ACTUALIZAR_PEDIDO), + controlador.actualizarPedido +); + +module.exports = ruteador; diff --git a/Pedidos/Rutas/indexPedidos.routes.js b/Pedidos/Rutas/indexPedidos.routes.js index 83334c54..30f5a42e 100644 --- a/Pedidos/Rutas/indexPedidos.routes.js +++ b/Pedidos/Rutas/indexPedidos.routes.js @@ -2,9 +2,12 @@ const express = require('express'); const ruteador = express.Router(); const rutasObtenerPedidos = require('@altertex/pedidos/rutasInd/obtenerPedidos.routes'); const rutasEliminarPedido = require('@altertex/pedidos/rutasInd/eliminarPedidos.routes'); +const rutasActualizarPedidos = require('@altertex/pedidos/rutasInd/actualizarPedidos.routes'); const RUTAS = require('@altertex/util/const/rutas'); ruteador.use(RUTAS.PEDIDOS.BASE, rutasObtenerPedidos); ruteador.use(RUTAS.PEDIDOS.BASE, rutasEliminarPedido); +ruteador.use(RUTAS.PEDIDOS.BASE, rutasActualizarPedidos); + module.exports = ruteador; diff --git a/Utilidades/Constantes/consultasPedidos.js b/Utilidades/Constantes/consultasPedidos.js index a92f3cfc..1a86c0b1 100644 --- a/Utilidades/Constantes/consultasPedidos.js +++ b/Utilidades/Constantes/consultasPedidos.js @@ -34,4 +34,14 @@ module.exports = { ELIMINAR_PEDIDO: ` DELETE FROM pedido WHERE idPedido = ?;`, + + ACTUALIZAR_PEDIDO: ` + UPDATE pedido SET + estado = ?, + precioTotal = ?, + idEnvio = ?, + idPago = ? + WHERE idPedido = ?; + `, + }; diff --git a/Utilidades/Constantes/mensajesPedidos.js b/Utilidades/Constantes/mensajesPedidos.js index 8e21d51d..978727e6 100644 --- a/Utilidades/Constantes/mensajesPedidos.js +++ b/Utilidades/Constantes/mensajesPedidos.js @@ -32,4 +32,13 @@ module.exports = { codigo: 500, mensaje: 'Ocurrió un error al eliminar el pedido.', }, + PEDIDO_ACTUALIZADO: { + codigo: 200, + mensaje: 'Pedido actualizado correctamente.', + }, + ERROR_ACTUALIZAR_PEDIDO: { + codigo: 400, + mensaje: 'Error al actualizar el pedido.', + }, + }; diff --git a/Utilidades/Constantes/rutas.js b/Utilidades/Constantes/rutas.js index 9a8bcf93..e89f407a 100644 --- a/Utilidades/Constantes/rutas.js +++ b/Utilidades/Constantes/rutas.js @@ -100,6 +100,7 @@ module.exports = { BASE: '/pedidos', CONSULTAR_LISTA: '/consultar-lista', ELIMINAR_PEDIDO: '/eliminar', + ACTUALIZAR_PEDIDO: '/actualizar-pedido', }, PAGOS: { BASE: '/pagos', From adc1f7d6b11282b778c2cf01b6abbe641b6b38c1 Mon Sep 17 00:00:00 2001 From: ArturoSanRod Date: Fri, 6 Jun 2025 11:27:43 -0600 Subject: [PATCH 088/116] No se manda el ID al hook --- Pedidos/Rutas/indexPedidos.routes.js | 1 - 1 file changed, 1 deletion(-) diff --git a/Pedidos/Rutas/indexPedidos.routes.js b/Pedidos/Rutas/indexPedidos.routes.js index 30f5a42e..16647e25 100644 --- a/Pedidos/Rutas/indexPedidos.routes.js +++ b/Pedidos/Rutas/indexPedidos.routes.js @@ -9,5 +9,4 @@ ruteador.use(RUTAS.PEDIDOS.BASE, rutasObtenerPedidos); ruteador.use(RUTAS.PEDIDOS.BASE, rutasEliminarPedido); ruteador.use(RUTAS.PEDIDOS.BASE, rutasActualizarPedidos); - module.exports = ruteador; From e2681538fa57f4a777977beb37a6c0d2af7592e4 Mon Sep 17 00:00:00 2001 From: NicoH00d Date: Fri, 6 Jun 2025 13:04:25 -0600 Subject: [PATCH 089/116] fix: mensaje de validacion --- .../Intermediarios/Validaciones/validarVarianteImportar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Utilidades/Intermediarios/Validaciones/validarVarianteImportar.js b/Utilidades/Intermediarios/Validaciones/validarVarianteImportar.js index 15c0b21e..a65542f0 100644 --- a/Utilidades/Intermediarios/Validaciones/validarVarianteImportar.js +++ b/Utilidades/Intermediarios/Validaciones/validarVarianteImportar.js @@ -36,7 +36,7 @@ module.exports = (variante) => { && (typeof variante.descripcion !== 'string' || variante.descripcion.length > 1000 || variante.descripcion.trim() === '') ) { return { - error: 'descripcion debe ser una cadena de texto y no exceder 1000 caracteres.', + error: 'descripcionVariante debe ser una cadena de texto y no exceder 1000 caracteres.', }; } From f1258496089142f480c13481e2e5186d5c4cde33 Mon Sep 17 00:00:00 2001 From: NicoH00d Date: Fri, 6 Jun 2025 13:08:31 -0600 Subject: [PATCH 090/116] fix: console error --- Utilidades/Intermediarios/generarSKUAuto.js | 1 - 1 file changed, 1 deletion(-) diff --git a/Utilidades/Intermediarios/generarSKUAuto.js b/Utilidades/Intermediarios/generarSKUAuto.js index 11262a7c..d8ffb993 100644 --- a/Utilidades/Intermediarios/generarSKUAuto.js +++ b/Utilidades/Intermediarios/generarSKUAuto.js @@ -39,7 +39,6 @@ const generarSKU = (nombreProducto, nombreVariante, valorOpcion) => { const codigoOpcion = obtenerCodigo(valorOpcion); return `${prefijo}-${codigoVariante}-${codigoOpcion}`; } catch { - console.error('Error generando SKU:', { nombreProducto, nombreVariante, valorOpcion }); return 'SKU-ERROR'; } }; From d7fb0ffe7ab0d200f1289ec0de0bf1b623969992 Mon Sep 17 00:00:00 2001 From: angieriosc Date: Fri, 6 Jun 2025 13:57:59 -0600 Subject: [PATCH 091/116] =?UTF-8?q?Fix:=20consulta,=20revisar=20entradas?= =?UTF-8?q?=20maliciosas,=20l=C3=B3gica=20controller=20y=20repositorio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controladores/actualizarPedido.controller.js | 2 +- .../Repositorios/repositorioActualizarPedido.js | 2 +- .../actualizarPedidos.routes.js | 2 ++ Utilidades/Constantes/consultasPedidos.js | 16 +++++++++------- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/Pedidos/Controladores/actualizarPedido.controller.js b/Pedidos/Controladores/actualizarPedido.controller.js index 85c9908c..072dbf73 100644 --- a/Pedidos/Controladores/actualizarPedido.controller.js +++ b/Pedidos/Controladores/actualizarPedido.controller.js @@ -7,7 +7,7 @@ const repositorio = require('@altertex/pedidos/repos/repositorioActualizarPedido */ exports.actualizarPedido = async (req, res) => { let datos; - + console.log('body', req.body); if (req.body.idPedido) { datos = [req.body]; } else if (req.body.cambios) { diff --git a/Pedidos/Datos/Repositorios/repositorioActualizarPedido.js b/Pedidos/Datos/Repositorios/repositorioActualizarPedido.js index e12b9445..b08156ef 100644 --- a/Pedidos/Datos/Repositorios/repositorioActualizarPedido.js +++ b/Pedidos/Datos/Repositorios/repositorioActualizarPedido.js @@ -17,8 +17,8 @@ exports.actualizarPedido = async (datos) => { return correrQuery(CONSULTAS.ACTUALIZAR_PEDIDO, [ estado, precioTotal, - idEnvio, idPago, + idEnvio, idPedido, ]); }) diff --git a/Pedidos/Rutas/RutasIndividuales/actualizarPedidos.routes.js b/Pedidos/Rutas/RutasIndividuales/actualizarPedidos.routes.js index 8d2d2eb5..90ba4c2a 100644 --- a/Pedidos/Rutas/RutasIndividuales/actualizarPedidos.routes.js +++ b/Pedidos/Rutas/RutasIndividuales/actualizarPedidos.routes.js @@ -8,6 +8,7 @@ const verificarPermisos = require('@altertex/util/inter/verificarPermisos'); const limitePeticiones = require('@altertex/util/inter/limitePeticiones'); const RUTAS = require('@altertex/util/const/rutas'); const PERMISOS = require('@altertex/util/const/permisos'); +const validarYSanitizar = require('@altertex/util/inter/validarYSanitizar'); // RF[62] - Actualizar Pedido ruteador.put( @@ -16,6 +17,7 @@ ruteador.put( autorizarToken, limitePeticiones, verificarPermisos(PERMISOS.ACTUALIZAR_PEDIDO), + validarYSanitizar, controlador.actualizarPedido ); diff --git a/Utilidades/Constantes/consultasPedidos.js b/Utilidades/Constantes/consultasPedidos.js index 1a86c0b1..1426a12d 100644 --- a/Utilidades/Constantes/consultasPedidos.js +++ b/Utilidades/Constantes/consultasPedidos.js @@ -36,12 +36,14 @@ module.exports = { WHERE idPedido = ?;`, ACTUALIZAR_PEDIDO: ` - UPDATE pedido SET - estado = ?, - precioTotal = ?, - idEnvio = ?, - idPago = ? - WHERE idPedido = ?; + UPDATE pedido + JOIN pago ON pedido.idPago = pago.idPago + JOIN envio ON pedido.idEnvio = envio.idEnvio + SET + pedido.estado = ?, + pedido.precioTotal = ?, + pago.estatus = ?, + envio.estado = ? + WHERE pedido.idPedido = ?; `, - }; From 3a85b01ea9f917d48ce1c0146f137af4069f9d87 Mon Sep 17 00:00:00 2001 From: Krlos7121 Date: Fri, 6 Jun 2025 14:06:14 -0600 Subject: [PATCH 092/116] feature/CIFM rf16 crear empleado --- .../Controladores/crearEmpleado.controller.js | 114 ++++++++++++------ .../Repositorios/repositorioCrearEmpleado.js | 65 +++++++++- .../RutasIndividuales/crearEmpleado.routes.js | 20 +-- Empleados/Rutas/indexEmpleados.routes.js | 5 + .../Controladores/crearUsuario.controller.js | 22 ++-- Utilidades/Constantes/consultasEmpleados.js | 25 ++++ .../Constantes/consultasImportarEmpleados.js | 17 ++- Utilidades/Constantes/mensajesEmpleados.js | 8 ++ 8 files changed, 200 insertions(+), 76 deletions(-) diff --git a/Empleados/Controladores/crearEmpleado.controller.js b/Empleados/Controladores/crearEmpleado.controller.js index 73dc5ac5..e0c49dc0 100644 --- a/Empleados/Controladores/crearEmpleado.controller.js +++ b/Empleados/Controladores/crearEmpleado.controller.js @@ -1,7 +1,5 @@ const bcrypt = require('bcryptjs'); -const MENSAJES = require('@altertex/util/const/mensajesEmpleados'); const repositorio = require('@altertex/emp/repos/repositorioCrearEmpleado'); -//RF[16] Crear empleado - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF16] /** * Controlador para crear un nuevo empleado. @@ -14,7 +12,7 @@ const repositorio = require('@altertex/emp/repos/repositorioCrearEmpleado'); * @param {Array} req.body - Cuerpo de la solicitud con los datos del nuevo empleado. * @param {string} req.body[].nombreCompleto - Nombre completo del usuario. * @param {string} req.body[].correoElectronico - Correo electrónico único del usuario. - * @param {string} req.body[].contrasena - Contraseña en texto plano. + * @param {string} req.body[].contrasenia - Contraseña en texto plano. * @param {string} req.body[].numeroTelefono - Número de teléfono (10 dígitos). * @param {string} req.body[].direccion - Dirección del usuario. * @param {string} req.body[].fechaNacimiento - Fecha de nacimiento en formato YYYY-MM-DD. @@ -35,11 +33,11 @@ const repositorio = require('@altertex/emp/repos/repositorioCrearEmpleado'); * - 500 si ocurre un error al crear el empleado. */ exports.crearEmpleado = async (req, res) => { - const [ + const { nombreCompleto, correoElectronico, contrasenia, - numberoTelefono, + numeroTelefono, direccion, fechaNacimiento, genero, @@ -51,22 +49,22 @@ exports.crearEmpleado = async (req, res) => { posicion, cantidadPuntos, antiguedad, - ] = req.body; + } = req.body; + // Validaciones críticas + if (!idCliente) { + return res.status(400).json({ mensaje: 'Cliente no seleccionado' }); + } if ( - !Array.isArray(req.body) || - req.body.length === 0 || !nombreCompleto || !correoElectronico || !contrasenia || - !numberoTelefono || + !numeroTelefono || !direccion || !fechaNacimiento || !genero || estatus === undefined || - !idRol || - idCliente === undefined || - (Array.isArray(idCliente) && idCliente.length === 0) || + idRol === undefined || !numeroEmergencia || !areaTrabajo || !posicion || @@ -75,44 +73,83 @@ exports.crearEmpleado = async (req, res) => { ) { return res.status(400).json({ mensaje: 'Faltan campos requeridos' }); } + + // Validaciones de formato y longitud + if (nombreCompleto.length > 75) { + return res.status(400).json({ mensaje: 'El nombre es demasiado largo' }); + } + if (!/^[A-Za-zÁÉÍÓÚáéíóúÑñ\s]+$/.test(nombreCompleto)) { + return res.status(400).json({ mensaje: 'El nombre solo puede contener letras y espacios' }); + } + if (correoElectronico.length > 75) { + return res.status(400).json({ mensaje: 'El correo es demasiado largo' }); + } const correoValido = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!correoValido.test(correoElectronico)) { - return res - .status(MENSAJES.CORREO_INVALIDO.codigo) - .json({ mensaje: MENSAJES.CORREO_INVALIDO.mensaje }); + return res.status(400).json({ mensaje: 'El correo electrónico no es válido' }); + } + if (contrasenia.length > 75) { + return res.status(400).json({ mensaje: 'La contraseña es demasiado larga' }); } const tieneCaracterEspecial = /[!@#$%^&*(),.?":{}|<>]/; const tieneMayuscula = /[A-Z]/; - if (contrasenia.length < 8) { + if ( + contrasenia.length < 8 || + !tieneCaracterEspecial.test(contrasenia) || + !tieneMayuscula.test(contrasenia) + ) { return res - .status(MENSAJES.CONTRASENA_DEBIL.codigo) - .json({ mensaje: MENSAJES.CONTRASENA_DEBIL.mensaje }); + .status(400) + .json({ + mensaje: + 'La contraseña es débil. Debe tener al menos 8 caracteres, una mayúscula y un caracter especial.', + }); } - if (!tieneCaracterEspecial.test(contrasenia)) { - return res - .status(MENSAJES.CONTRASENA_DEBIL.codigo) - .json({ mensaje: MENSAJES.CONTRASENA_DEBIL.mensaje }); + if (direccion.length > 150) { + return res.status(400).json({ mensaje: 'La dirección es demasiado larga' }); + } + if (posicion.length > 75) { + return res.status(400).json({ mensaje: 'La posición es demasiado larga' }); } - if (!tieneMayuscula.test(contrasenia)) { + if (areaTrabajo.length > 75) { + return res.status(400).json({ mensaje: 'El área de trabajo es demasiado larga' }); + } + if (genero.length > 20) { + return res.status(400).json({ mensaje: 'El género es demasiado largo' }); + } + if (isNaN(numeroEmergencia)) { + return res.status(400).json({ mensaje: 'El número de emergencia no es válido' }); + } + if (!/^\d+$/.test(String(cantidadPuntos)) || Number(cantidadPuntos) < 0) { return res - .status(MENSAJES.CONTRASENA_DEBIL.codigo) - .json({ mensaje: MENSAJES.CONTRASENA_DEBIL.mensaje }); + .status(400) + .json({ mensaje: 'Los puntos deben ser un número entero mayor o igual a 0' }); } - const telefonoValido = /^\d{10}$/; - if (!telefonoValido.test(numberoTelefono)) { + if (!telefonoValido.test(numeroTelefono)) { + return res + .status(400) + .json({ mensaje: 'El número de teléfono debe tener 10 dígitos numéricos' }); + } + const fechaRegex = /^\d{4}-\d{2}-\d{2}$/; + if (!fechaRegex.test(fechaNacimiento) || isNaN(Date.parse(fechaNacimiento))) { + return res + .status(400) + .json({ mensaje: 'La fecha de nacimiento no tiene un formato válido (YYYY-MM-DD)' }); + } + if (!fechaRegex.test(antiguedad) || isNaN(Date.parse(antiguedad))) { return res - .status(MENSAJES.TELEFONO_INVALIDO.codigo) - .json({ mensaje: MENSAJES.TELEFONO_INVALIDO.mensaje }); + .status(400) + .json({ mensaje: 'La antigüedad no tiene un formato válido (YYYY-MM-DD)' }); } try { const contraseniaEncriptada = await bcrypt.hash(contrasenia, 10); - const resultado = await repositorio.crearEmpleado( + const resultado = await repositorio.crearEmpleado({ nombreCompleto, correoElectronico, - contraseniaEncriptada, - numberoTelefono, + contrasenia: contraseniaEncriptada, + numeroTelefono, direccion, fechaNacimiento, genero, @@ -123,17 +160,14 @@ exports.crearEmpleado = async (req, res) => { areaTrabajo, posicion, cantidadPuntos, - antiguedad - ); - return res.status(MENSAJES_EMPLEADOS.CREACION_EXITOSA.codigo).json({ - mensaje: MENSAJES.CREACION_EXITOSA.mensaje, + antiguedad, + }); + return res.status(201).json({ + mensaje: 'Empleado creado exitosamente', datos: resultado, }); } catch (error) { console.error('Error al crear empleado:', error); - return res.status(MENSAJES.ERROR_CREACION.codigo).json({ - mensaje: MENSAJES.ERROR_CREACION.mensaje, - error: error.message, - }); + return res.status(400).json({ mensaje: error.message }); } }; diff --git a/Empleados/Datos/Repositorios/repositorioCrearEmpleado.js b/Empleados/Datos/Repositorios/repositorioCrearEmpleado.js index 65406cd1..f08ee4fe 100644 --- a/Empleados/Datos/Repositorios/repositorioCrearEmpleado.js +++ b/Empleados/Datos/Repositorios/repositorioCrearEmpleado.js @@ -1,7 +1,9 @@ const db = require('@altertex/util/bd/db'); +const ROL_PREDETERMINADO = 3; // ID del rol por defecto para empleados const correrQuery = require('@altertex/util/ser/correrQuery'); const MENSAJES = require('@altertex/util/const/mensajesEmpleados'); const CONSULTAS_EMPLEADOS = require('@altertex/util/const/consultasEmpleados'); +const CONSULTAS_IMPORTAR_EMPLEADOS = require('@altertex/util/const/consultasImportarEmpleados'); //RF[16] Crear empleado - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF16] @@ -50,6 +52,34 @@ exports.crearEmpleado = async (empleado) => { throw new Error(`Correo ya registrado: ${empleado.correoElectronico}`); } + // Validar fecha de nacimiento + if (!/^\d{4}-\d{2}-\d{2}$/.test(empleado.fechaNacimiento)) { + throw new Error('Fecha de nacimiento inválida. Debe ser en formato YYYY-MM-DD.'); + } + const fechaNacimiento = new Date(empleado.fechaNacimiento); + if (isNaN(fechaNacimiento.getTime())) { + throw new Error('Fecha de nacimiento inválida.'); + } + const hoy = new Date(); + if (fechaNacimiento > hoy) { + throw new Error('La fecha de nacimiento no puede ser futura.'); + } + if (fechaNacimiento.getFullYear() < 1900) { + throw new Error('La fecha de nacimiento no puede ser anterior a 1900.'); + } + + // Validar que sea mayor o igual a 18 años + const edad = hoy.getFullYear() - fechaNacimiento.getFullYear(); + const mes = hoy.getMonth() - fechaNacimiento.getMonth(); + const dia = hoy.getDate() - fechaNacimiento.getDate(); + let edadFinal = edad; + if (mes < 0 || (mes === 0 && dia < 0)) { + edadFinal--; + } + if (edadFinal < 18) { + throw new Error('El empleado debe tener al menos 18 años.'); + } + // Validar teléfono duplicado const [telefonoExistente] = await conn.query( CONSULTAS_IMPORTAR_EMPLEADOS.VALIDAR_TELEFONO_DUPLICADO, @@ -59,11 +89,34 @@ exports.crearEmpleado = async (empleado) => { throw new Error(`Teléfono ya registrado: ${empleado.numeroTelefono}`); } + //Validar antigüedad + if (!/^\d{4}-\d{2}-\d{2}$/.test(empleado.antiguedad)) { + throw new Error('Antigüedad inválida. Debe ser en formato YYYY-MM-DD.'); + } + const fechaAntiguedad = new Date(empleado.antiguedad); + if (isNaN(fechaAntiguedad.getTime())) { + throw new Error('Antigüedad inválida.'); + } + if (fechaAntiguedad > hoy) { + throw new Error('La antigüedad no puede ser futura.'); + } + if (fechaAntiguedad.getFullYear() < 1900) { + throw new Error('La antigüedad no puede ser anterior a 1900.'); + } + // Validar que la antigüedad sea menor o igual a la fecha de nacimiento + if (fechaAntiguedad < fechaNacimiento) { + throw new Error('La antigüedad no puede ser anterior a la fecha de nacimiento.'); + } + // Validar que la antigüedad sea menor o igual a la fecha actual + if (fechaAntiguedad > hoy) { + throw new Error('La antigüedad no puede ser posterior a la fecha actual.'); + } + // Insertar usuario - const [resultadoUsuario] = await conn.query(CONSULTAS_IMPORTAR_EMPLEADOS.INSERTAR_USUARIO, [ + const [resultadoUsuario] = await conn.query(CONSULTAS_EMPLEADOS.INSERTAR_USUARIO, [ empleado.nombreCompleto, empleado.correoElectronico, - empleado.contrasena, + empleado.contrasenia, empleado.numeroTelefono, empleado.direccion, empleado.fechaNacimiento, @@ -73,18 +126,18 @@ exports.crearEmpleado = async (empleado) => { const idUsuario = resultadoUsuario.insertId; // Insertar rol - await conn.query(CONSULTAS_IMPORTAR_EMPLEADOS.INSERTAR_ROL, [idUsuario, DEFAULT_ROLE_ID]); + await conn.query(CONSULTAS_EMPLEADOS.INSERTAR_ROL, [idUsuario, ROL_PREDETERMINADO]); // Insertar asociación usuario-cliente const listaClientes = Array.isArray(empleado.idCliente) ? empleado.idCliente : [empleado.idCliente]; for (const idCli of listaClientes) { - await conn.query(CONSULTAS_IMPORTAR_EMPLEADOS.INSERTAR_USUARIO_CLIENTE, [idUsuario, idCli]); + await conn.query(CONSULTAS_EMPLEADOS.INSERTAR_USUARIO_CLIENTE, [idUsuario, idCli]); } // Insertar empleado - await conn.query(CONSULTAS_IMPORTAR_EMPLEADOS.INSERTAR_EMPLEADO, [ + await conn.query(CONSULTAS_EMPLEADOS.INSERTAR_EMPLEADO, [ idUsuario, empleado.idCliente, empleado.numeroEmergencia, @@ -97,7 +150,7 @@ exports.crearEmpleado = async (empleado) => { await conn.commit(); } catch (err) { await conn.rollback(); - throw new Error(`Error en importación: ${err.message}`); + throw new Error(`${err.message}`); } finally { if (conn) conn.release(); } diff --git a/Empleados/Rutas/RutasIndividuales/crearEmpleado.routes.js b/Empleados/Rutas/RutasIndividuales/crearEmpleado.routes.js index 9cffd0bb..f883d48f 100644 --- a/Empleados/Rutas/RutasIndividuales/crearEmpleado.routes.js +++ b/Empleados/Rutas/RutasIndividuales/crearEmpleado.routes.js @@ -1,17 +1,17 @@ +//RF16 - Crear Empleado - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF16] + const express = require('express'); const ruteador = express.Router(); -const PERMISOS = require('@altertex/util/const/permisos'); -const RUTAS = require('@altertex/util/const/rutas'); -const controlador = require('@altertex/emp/ctrl/actualizarEmpleado.controller'); +const controlador = require('@altertex/emp/ctrl/crearEmpleado.controller'); const revisarApiKey = require('@altertex/util/inter/revisarApiKey'); const autorizarToken = require('@altertex/util/inter/autorizarToken'); -const revisarPermisos = require('@altertex/util/inter/verificarPermisos'); +const verificarPermisos = require('@altertex/util/inter/verificarPermisos'); +const PERMISOS = require('@altertex/util/const/permisos'); +const RUTAS = require('@altertex/util/const/rutas'); const validarYSanitizar = require('@altertex/util/inter/validarYSanitizar'); const limitePeticionesDiarias = require('@altertex/util/inter/limitePeticiones'); -//RF[16] Crear Empleado - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF16] - /** * @swagger * /api/empleados/crear: @@ -193,11 +193,11 @@ const limitePeticionesDiarias = require('@altertex/util/inter/limitePeticiones') ruteador.post( RUTAS.EMPLEADOS.CREAR, - revisarApiKey, + revisarApiKey(), autorizarToken, - revisarPermisos([PERMISOS.EMPLEADOS.CREAR]), - validarYSanitizar.validarCrearEmpleado, - limitePeticionesDiarias.limitarPeticionesDiarias, + limitePeticionesDiarias, + validarYSanitizar, + verificarPermisos(PERMISOS.CREAR_EMPLEADO), controlador.crearEmpleado ); diff --git a/Empleados/Rutas/indexEmpleados.routes.js b/Empleados/Rutas/indexEmpleados.routes.js index f9ff9fe4..8946c185 100644 --- a/Empleados/Rutas/indexEmpleados.routes.js +++ b/Empleados/Rutas/indexEmpleados.routes.js @@ -1,6 +1,7 @@ const express = require('express'); const ruteador = express.Router(); const rutasConsultarListaGrupos = require('@altertex/emp/rutasInd/consultarListaGrupos.routes'); +const rutasCrearEmpleado = require('@altertex/emp/rutasInd/crearEmpleado.routes'); const rutasConsultarLista = require('@altertex/emp/rutasInd/consultarLista.routes'); const rutasEliminarGrupo = require('@altertex/emp/rutasInd/eliminarGrupoEmpleados.routes'); const rutasEliminarEmpleado = require('@altertex/emp/rutasInd/eliminarEmpleado.routes'); @@ -11,6 +12,7 @@ const rutasCrearGrupo = require('@altertex/emp/rutasInd/crearGrupoEmpleados.rout const rutasActualizarEmpleado = require('@altertex/emp/rutasInd/actualizarEmpleado.routes'); const RUTAS = require('@altertex/util/const/rutas'); +const rutas = require('@altertex/util/const/rutas'); //RF22 - Consulta Lista de Grupo Empleados - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF22 ruteador.use(RUTAS.EMPLEADOS.BASE, rutasConsultarListaGrupos); @@ -29,4 +31,7 @@ ruteador.use(RUTAS.EMPLEADOS.BASE, rutasCrearGrupo); //RF19 - Actualizar Empleado - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF19 ruteador.use(RUTAS.EMPLEADOS.BASE, rutasActualizarEmpleado); + +//RF16 - Crear Empleado - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF16 +ruteador.use(RUTAS.EMPLEADOS.BASE, rutasCrearEmpleado); module.exports = ruteador; diff --git a/Usuarios/Controladores/crearUsuario.controller.js b/Usuarios/Controladores/crearUsuario.controller.js index ddac86e4..f5542d72 100644 --- a/Usuarios/Controladores/crearUsuario.controller.js +++ b/Usuarios/Controladores/crearUsuario.controller.js @@ -43,17 +43,17 @@ exports.crearUsuario = async (req, res) => { } = req.body; if ( - !nombreCompleto - || !correoElectronico - || !contrasenia - || !numeroTelefono - || !direccion - || !fechaNacimiento - || !genero - || estatus === undefined - || !idRol - || idCliente === undefined - || (Array.isArray(idCliente) && idCliente.length === 0) + !nombreCompleto || + !correoElectronico || + !contrasenia || + !numeroTelefono || + !direccion || + !fechaNacimiento || + !genero || + estatus === undefined || + !idRol || + idCliente === undefined || + (Array.isArray(idCliente) && idCliente.length === 0) ) { return res.status(400).json({ mensaje: 'Faltan campos requeridos' }); } diff --git a/Utilidades/Constantes/consultasEmpleados.js b/Utilidades/Constantes/consultasEmpleados.js index 439c35b3..877e60ad 100644 --- a/Utilidades/Constantes/consultasEmpleados.js +++ b/Utilidades/Constantes/consultasEmpleados.js @@ -37,4 +37,29 @@ module.exports = { ELSE 'OK' END AS resultado; `, + + INSERTAR_USUARIO: ` + INSERT INTO usuario + (nombreCompleto, correoElectronico, contrasenia, numeroTelefono, direccion, fechaNacimiento, genero, estatus) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + + `, + + INSERTAR_ROL: ` + INSERT INTO usuario_rol (idUsuario, idRol) + VALUES (?, ?) + `, + + INSERTAR_USUARIO_CLIENTE: ` + INSERT INTO usuario_cliente (idUsuario, idCliente) + VALUES (?, ?) + `, + + INSERTAR_EMPLEADO: ` + INSERT INTO empleado ( + idUsuario, idCliente, numeroEmergencia, + areaTrabajo, posicion, cantidadPuntos, antiguedad + ) + VALUES (?, ?, ?, ?, ?, ?, ?) + `, }; diff --git a/Utilidades/Constantes/consultasImportarEmpleados.js b/Utilidades/Constantes/consultasImportarEmpleados.js index 9701c024..abaded26 100644 --- a/Utilidades/Constantes/consultasImportarEmpleados.js +++ b/Utilidades/Constantes/consultasImportarEmpleados.js @@ -1,28 +1,27 @@ module.exports = { - - VALIDAR_CORREOS_DUPLICADOS: ` + VALIDAR_CORREOS_DUPLICADOS: ` SELECT correoElectronico FROM usuario WHERE correoElectronico IN (?) `, - VALIDAR_TELEFONO_DUPLICADO: ` + VALIDAR_TELEFONO_DUPLICADO: ` SELECT numeroTelefono FROM usuario WHERE numeroTelefono IN (?) `, - INSERTAR_USUARIO_EN_VOLUMEN:` + INSERTAR_USUARIO_EN_VOLUMEN: ` INSERT INTO usuario (nombreCompleto, correoElectronico, contrasenia, numeroTelefono, direccion, fechaNacimiento, genero, estatus) VALUES ? `, - OBTENER_ID_GENERADOS: ` + OBTENER_ID_GENERADOS: ` SELECT idUsuario, correoElectronico FROM usuario WHERE correoElectronico IN (?) `, - INSERTAR_ROLES_EN_VOLUMEN:` + INSERTAR_ROLES_EN_VOLUMEN: ` INSERT INTO usuario_rol (idUsuario, idRol) VALUES ? `, - INSERTAR_USUARIO_CLIENTE_EN_VOLUMEN:` + INSERTAR_USUARIO_CLIENTE_EN_VOLUMEN: ` INSERT INTO usuario_cliente (idUsuario, idCliente) VALUES ? `, - INSERTAR_EMPLEADOS_EN_VOLUMEN:` + INSERTAR_EMPLEADOS_EN_VOLUMEN: ` INSERT INTO empleado (idUsuario, idCliente, numeroEmergencia, areaTrabajo, posicion, cantidadPuntos, antiguedad) VALUES ? `, -} \ No newline at end of file +}; diff --git a/Utilidades/Constantes/mensajesEmpleados.js b/Utilidades/Constantes/mensajesEmpleados.js index 3a06f8da..f524bb8d 100644 --- a/Utilidades/Constantes/mensajesEmpleados.js +++ b/Utilidades/Constantes/mensajesEmpleados.js @@ -36,6 +36,10 @@ module.exports = { codigo: 400, mensaje: 'Error al crear', }, + CAMPOS_REQUERIDOS: { + codigo: 400, + mensaje: 'Faltan campos requeridos', + }, // 403 - Forbidden PERMISO_DENEGADO: { @@ -85,4 +89,8 @@ module.exports = { codigo: 'GRUPO_NOMBRE_REPETIDO', mensaje: 'Ya existe un grupo con ese nombre.', }, + ERROR_CREACION: { + codigo: 500, + mensaje: 'Error al crear el empleado', + }, }; From 70e12abb93e99d0a4cfa8b0fb49d22ad36a09539 Mon Sep 17 00:00:00 2001 From: Krlos7121 Date: Fri, 6 Jun 2025 14:17:59 -0600 Subject: [PATCH 093/116] fix: arreglar errores de eslint --- .../Controladores/crearEmpleado.controller.js | 34 +++++++++---------- .../Repositorios/repositorioCrearEmpleado.js | 9 +++-- Empleados/Rutas/indexEmpleados.routes.js | 1 - .../Controladores/crearUsuario.controller.js | 22 ++++++------ Utilidades/Constantes/consultasEmpleados.js | 7 ---- 5 files changed, 32 insertions(+), 41 deletions(-) diff --git a/Empleados/Controladores/crearEmpleado.controller.js b/Empleados/Controladores/crearEmpleado.controller.js index e0c49dc0..10df00d4 100644 --- a/Empleados/Controladores/crearEmpleado.controller.js +++ b/Empleados/Controladores/crearEmpleado.controller.js @@ -56,20 +56,20 @@ exports.crearEmpleado = async (req, res) => { return res.status(400).json({ mensaje: 'Cliente no seleccionado' }); } if ( - !nombreCompleto || - !correoElectronico || - !contrasenia || - !numeroTelefono || - !direccion || - !fechaNacimiento || - !genero || - estatus === undefined || - idRol === undefined || - !numeroEmergencia || - !areaTrabajo || - !posicion || - cantidadPuntos === undefined || - !antiguedad + !nombreCompleto + || !correoElectronico + || !contrasenia + || !numeroTelefono + || !direccion + || !fechaNacimiento + || !genero + || estatus === undefined + || idRol === undefined + || !numeroEmergencia + || !areaTrabajo + || !posicion + || cantidadPuntos === undefined + || !antiguedad ) { return res.status(400).json({ mensaje: 'Faltan campos requeridos' }); } @@ -94,9 +94,9 @@ exports.crearEmpleado = async (req, res) => { const tieneCaracterEspecial = /[!@#$%^&*(),.?":{}|<>]/; const tieneMayuscula = /[A-Z]/; if ( - contrasenia.length < 8 || - !tieneCaracterEspecial.test(contrasenia) || - !tieneMayuscula.test(contrasenia) + contrasenia.length < 8 + || !tieneCaracterEspecial.test(contrasenia) + || !tieneMayuscula.test(contrasenia) ) { return res .status(400) diff --git a/Empleados/Datos/Repositorios/repositorioCrearEmpleado.js b/Empleados/Datos/Repositorios/repositorioCrearEmpleado.js index f08ee4fe..ed0ed8f2 100644 --- a/Empleados/Datos/Repositorios/repositorioCrearEmpleado.js +++ b/Empleados/Datos/Repositorios/repositorioCrearEmpleado.js @@ -1,7 +1,6 @@ const db = require('@altertex/util/bd/db'); const ROL_PREDETERMINADO = 3; // ID del rol por defecto para empleados -const correrQuery = require('@altertex/util/ser/correrQuery'); -const MENSAJES = require('@altertex/util/const/mensajesEmpleados'); + const CONSULTAS_EMPLEADOS = require('@altertex/util/const/consultasEmpleados'); const CONSULTAS_IMPORTAR_EMPLEADOS = require('@altertex/util/const/consultasImportarEmpleados'); @@ -12,7 +11,7 @@ const CONSULTAS_IMPORTAR_EMPLEADOS = require('@altertex/util/const/consultasImpo * * @async * @function crearEmpleado - * @param {Object} empleado - Objeto con los datos del nuevo empleado. + * @param {object} empleado - Objeto con los datos del nuevo empleado. * @param {string} empleado.nombreCompleto - Nombre completo del usuario. * @param {string} empleado.correoElectronico - Correo electrónico único del usuario. * @param {string} empleado.contrasena - Contraseña en texto plano (ya hasheada). @@ -31,7 +30,7 @@ const CONSULTAS_IMPORTAR_EMPLEADOS = require('@altertex/util/const/consultasImpo * @throws {Error} Si el parámetro "empleado" no es un objeto válido o está vacío. * @throws {Error} Si ocurre cualquier fallo durante la inserción en la transacción. * - * @returns {Promise} Resuelve con un objeto que contiene el ID del nuevo empleado y su usuario. + * @returns {Promise} Resuelve con un objeto que contiene el ID del nuevo empleado y su usuario. */ exports.crearEmpleado = async (empleado) => { if (!empleado || typeof empleado !== 'object') { @@ -74,7 +73,7 @@ exports.crearEmpleado = async (empleado) => { const dia = hoy.getDate() - fechaNacimiento.getDate(); let edadFinal = edad; if (mes < 0 || (mes === 0 && dia < 0)) { - edadFinal--; + edadFinal -= 1; } if (edadFinal < 18) { throw new Error('El empleado debe tener al menos 18 años.'); diff --git a/Empleados/Rutas/indexEmpleados.routes.js b/Empleados/Rutas/indexEmpleados.routes.js index 0886b47e..7f39b6f5 100644 --- a/Empleados/Rutas/indexEmpleados.routes.js +++ b/Empleados/Rutas/indexEmpleados.routes.js @@ -13,7 +13,6 @@ const rutasActualizarEmpleado = require('@altertex/emp/rutasInd/actualizarEmplea const rutasActualizarGrupo = require('@altertex/emp/rutasInd/actualizarGrupoEmpleados.routes'); const RUTAS = require('@altertex/util/const/rutas'); -const rutas = require('@altertex/util/const/rutas'); //RF22 - Consulta Lista de Grupo Empleados - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF22 ruteador.use(RUTAS.EMPLEADOS.BASE, rutasConsultarListaGrupos); diff --git a/Usuarios/Controladores/crearUsuario.controller.js b/Usuarios/Controladores/crearUsuario.controller.js index f5542d72..ddac86e4 100644 --- a/Usuarios/Controladores/crearUsuario.controller.js +++ b/Usuarios/Controladores/crearUsuario.controller.js @@ -43,17 +43,17 @@ exports.crearUsuario = async (req, res) => { } = req.body; if ( - !nombreCompleto || - !correoElectronico || - !contrasenia || - !numeroTelefono || - !direccion || - !fechaNacimiento || - !genero || - estatus === undefined || - !idRol || - idCliente === undefined || - (Array.isArray(idCliente) && idCliente.length === 0) + !nombreCompleto + || !correoElectronico + || !contrasenia + || !numeroTelefono + || !direccion + || !fechaNacimiento + || !genero + || estatus === undefined + || !idRol + || idCliente === undefined + || (Array.isArray(idCliente) && idCliente.length === 0) ) { return res.status(400).json({ mensaje: 'Faltan campos requeridos' }); } diff --git a/Utilidades/Constantes/consultasEmpleados.js b/Utilidades/Constantes/consultasEmpleados.js index ca6dd868..fa19ab29 100644 --- a/Utilidades/Constantes/consultasEmpleados.js +++ b/Utilidades/Constantes/consultasEmpleados.js @@ -78,11 +78,4 @@ module.exports = { VALUES (?, ?) `, - INSERTAR_EMPLEADO: ` - INSERT INTO empleado ( - idUsuario, idCliente, numeroEmergencia, - areaTrabajo, posicion, cantidadPuntos, antiguedad - ) - VALUES (?, ?, ?, ?, ?, ?, ?) - `, }; From 88c3dedc8e59f2a12090a2b036929be52945274d Mon Sep 17 00:00:00 2001 From: NicoH00d Date: Fri, 6 Jun 2025 14:26:02 -0600 Subject: [PATCH 094/116] feat: Agregar pruebas automaticas --- .../Usuarios/crearUsuario.controller.test.js | 100 ++++++++++++++++++ jest.config.js | 9 ++ 2 files changed, 109 insertions(+) create mode 100644 _tests_/Usuarios/crearUsuario.controller.test.js diff --git a/_tests_/Usuarios/crearUsuario.controller.test.js b/_tests_/Usuarios/crearUsuario.controller.test.js new file mode 100644 index 00000000..9e4496ed --- /dev/null +++ b/_tests_/Usuarios/crearUsuario.controller.test.js @@ -0,0 +1,100 @@ +jest.mock('@altertex/usu/repos/repositorioCrearUsuario', () => ({ + crearUsuarioConAsociaciones: jest.fn(), +})); + +const repositorio = require('@altertex/usu/repos/repositorioCrearUsuario'); +const controlador = require('@altertex/usu/ctrl/crearUsuario.controller'); +const MENSAJES_USUARIOS = require('@altertex/util/const/mensajesUsuarios'); + +describe('Controlador de Crear Usuario', () => { + let req; + let res; + + const datosMock = { + nombreCompleto: 'María López', + correoElectronico: 'maria@correo.com', + contrasenia: 'Contrasenia1.', + numeroTelefono: '5512345678', + direccion: 'Calle Falsa 123', + fechaNacimiento: '1990-01-01', + genero: 'Femenino', + estatus: true, + idRol: 2, + idCliente: [1], + }; + + beforeEach(() => { + jest.clearAllMocks(); + req = { body: { ...datosMock } }; + res = { + status: jest.fn().mockReturnThis(), + json: jest.fn(), + }; + }); + + test('Debe crear un usuario exitosamente', async () => { + repositorio.crearUsuarioConAsociaciones.mockResolvedValue({ idUsuario: 101 }); + + await controlador.crearUsuario(req, res); + + expect(res.status).toHaveBeenCalledWith(MENSAJES_USUARIOS.USUARIO_CREADO.codigo); + expect(res.json).toHaveBeenCalledWith({ + mensaje: MENSAJES_USUARIOS.USUARIO_CREADO.mensaje, + idUsuario: 101, + }); + expect(repositorio.crearUsuarioConAsociaciones).toHaveBeenCalled(); + }); + + test('Debe rechazar si faltan campos requeridos', async () => { + req.body = { ...datosMock, nombreCompleto: undefined }; + + await controlador.crearUsuario(req, res); + + expect(res.status).toHaveBeenCalledWith(400); + expect(res.json).toHaveBeenCalledWith({ mensaje: 'Faltan campos requeridos' }); + }); + + test('Debe rechazar correo inválido', async () => { + req.body.correoElectronico = 'correo-no-valido'; + + await controlador.crearUsuario(req, res); + + expect(res.status).toHaveBeenCalledWith(MENSAJES_USUARIOS.CORREO_INVALIDO.codigo); + expect(res.json).toHaveBeenCalledWith({ + mensaje: MENSAJES_USUARIOS.CORREO_INVALIDO.mensaje, + }); + }); + + test('Debe rechazar contraseña débil (sin mayúscula)', async () => { + req.body.contrasenia = 'contrasenia$1'; + + await controlador.crearUsuario(req, res); + + expect(res.status).toHaveBeenCalledWith(MENSAJES_USUARIOS.CONTRASENA_DEBIL.codigo); + expect(res.json).toHaveBeenCalledWith({ + mensaje: 'La contraseña debe contener al menos una letra mayúscula.', + }); + }); + + test('Debe rechazar número telefónico inválido', async () => { + req.body.numeroTelefono = 'abc123'; + + await controlador.crearUsuario(req, res); + + expect(res.status).toHaveBeenCalledWith(MENSAJES_USUARIOS.TELEFONO_INVALIDO.codigo); + expect(res.json).toHaveBeenCalledWith({ + mensaje: MENSAJES_USUARIOS.TELEFONO_INVALIDO.mensaje, + }); + }); + + test('Debe manejar error del repositorio', async () => { + repositorio.crearUsuarioConAsociaciones.mockRejectedValue(new Error('Fallo')); + + await controlador.crearUsuario(req, res); + + expect(res.status).toHaveBeenCalledWith(MENSAJES_USUARIOS.ERROR_CREAR_USUARIO.codigo); + expect(res.json).toHaveBeenCalledWith({ + mensaje: MENSAJES_USUARIOS.ERROR_CREAR_USUARIO.mensaje, + }); + }); +}); diff --git a/jest.config.js b/jest.config.js index 6466bef9..23920c9b 100644 --- a/jest.config.js +++ b/jest.config.js @@ -68,7 +68,16 @@ module.exports = { "^@altertex/eve/datos/(.*)$": "/Eventos/Datos/$1", "^@altertex/eve/(.*)$": "/Eventos/$1", + // Usuarios module mappings + '^@altertex/usu/ctrl/(.*)$': '/Usuarios/Controladores/$1', + '^@altertex/usu/repos/(.*)$': '/Usuarios/Datos/Repositorios/$1', + '^@altertex/usu/rutasInd/(.*)$': '/Usuarios/Rutas/RutasIndividuales/$1', + '^@altertex/usu/rutas/(.*)$': '/Usuarios/Rutas/$1', + '^@altertex/usu/datos/(.*)$': '/Usuarios/Datos/$1', + '^@altertex/usu/(.*)$': '/Usuarios/$1', + // Generic mapping as fallback '^@altertex/(.*)$': '/$1', + }, }; From 93e3a1610774eda2f317bf771ad5542b95b2f81c Mon Sep 17 00:00:00 2001 From: Krlos7121 Date: Fri, 6 Jun 2025 14:31:51 -0600 Subject: [PATCH 095/116] hotfix: quitar console.error --- Empleados/Controladores/crearEmpleado.controller.js | 1 - 1 file changed, 1 deletion(-) diff --git a/Empleados/Controladores/crearEmpleado.controller.js b/Empleados/Controladores/crearEmpleado.controller.js index 10df00d4..4ecb4a14 100644 --- a/Empleados/Controladores/crearEmpleado.controller.js +++ b/Empleados/Controladores/crearEmpleado.controller.js @@ -167,7 +167,6 @@ exports.crearEmpleado = async (req, res) => { datos: resultado, }); } catch (error) { - console.error('Error al crear empleado:', error); return res.status(400).json({ mensaje: error.message }); } }; From cba22a7c93ef001b923291d8710db176bdc0a196 Mon Sep 17 00:00:00 2001 From: NicoH00d Date: Fri, 6 Jun 2025 15:00:40 -0600 Subject: [PATCH 096/116] Fix: mensajes en constantes --- .../importarProductos.controller.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/Productos/Controladores/importarProductos.controller.js b/Productos/Controladores/importarProductos.controller.js index 99f7f858..89c8b0ca 100644 --- a/Productos/Controladores/importarProductos.controller.js +++ b/Productos/Controladores/importarProductos.controller.js @@ -38,10 +38,16 @@ const { proveedorExiste } = require('@altertex/pro/repos/repositorioValidarProve */ exports.importarProductos = async (req, res) => { const idCliente = parseInt(req.user.clienteSeleccionado); - const productos = req.body; + const productos = req.body; + + const MENSAJE_PRODUCTOS_INVALIDOS = 'No se recibieron productos válidos.' + const MENSAJE_VARIANTES_INVALIDAS = 'No se recibieron productos válidos.' + const MENSAJE_ERRORES_ARCHIVO = 'Se encontraron errores en el archivo.' + const IMPORTACION_EXITOSA = 'Importación completada exitosamente.'; + const ERROR_AL_IMPORTAR = 'Error al importar productos.'; if (!Array.isArray(productos) || productos.length === 0) { - return res.status(400).json({ mensaje: 'No se recibieron productos válidos.' }); + return res.status(400).json({ mensaje: MENSAJE_PRODUCTOS_INVALIDOS}); } const errores = []; @@ -69,7 +75,7 @@ exports.importarProductos = async (req, res) => { } if (!Array.isArray(variantes) || variantes.length === 0) { - errores.push({ fila, error: 'Producto sin variantes válidas.' }); + errores.push({ fila, error: MENSAJE_VARIANTES_INVALIDAS }); continue; } @@ -91,7 +97,7 @@ exports.importarProductos = async (req, res) => { if (errores.length > 0) { await conexion.rollback(); return res.status(200).json({ - mensaje: 'Se encontraron errores en el archivo.', + mensaje: MENSAJE_ERRORES_ARCHIVO, errores, }); } @@ -119,14 +125,14 @@ exports.importarProductos = async (req, res) => { await conexion.commit(); return res.status(200).json({ - mensaje: 'Importación completada exitosamente.', + mensaje: IMPORTACION_EXITOSA, errores: null, }); } catch (err) { if (conexion) await conexion.rollback(); return res.status(500).json({ - mensaje: 'Error al importar productos.', + mensaje: ERROR_AL_IMPORTAR, error: err.message, }); } finally { From 7eba807cea71ea302e46ce66d6aa0038d26b366f Mon Sep 17 00:00:00 2001 From: NicoH00d Date: Fri, 6 Jun 2025 15:01:59 -0600 Subject: [PATCH 097/116] FIX: renombrar variable --- Productos/Controladores/importarProductos.controller.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Productos/Controladores/importarProductos.controller.js b/Productos/Controladores/importarProductos.controller.js index 89c8b0ca..35c59d7a 100644 --- a/Productos/Controladores/importarProductos.controller.js +++ b/Productos/Controladores/importarProductos.controller.js @@ -102,8 +102,8 @@ exports.importarProductos = async (req, res) => { }); } const generarSKUConsecutivo = crearGeneradorSKUConsecutivo(); - for (let im = 0; im < productos.length; im += 1) { - const { producto, variantes } = productos[im]; + for (let indice = 0; indice < productos.length; indice += 1) { + const { producto, variantes } = productos[indice]; const idProducto = await repositorioCrearProducto.crearProducto(idCliente, producto); for (const variante of variantes) { From 4845d6db690a477547b66e95373f6afc8cd1972e Mon Sep 17 00:00:00 2001 From: PAOLA MARIA garrido Date: Fri, 6 Jun 2025 15:15:43 -0600 Subject: [PATCH 098/116] =?UTF-8?q?test:=20agregar=20pruebas=20autom=C3=A1?= =?UTF-8?q?ticas=20en=20los=20modulos=20de=20clientes,=20categor=C3=ADas?= =?UTF-8?q?=20y=20empleados?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...onsultarListaCategorias.controller.test.js | 89 +++++++++++++ .../eliminarCliente.controller.test.js | 92 +++++++++++++ .../Clientes/leerCliente.controller.test.js | 124 ++++++++++++++++++ .../exportarEmpleados.controller.test.js | 106 +++++++++++++++ jest.config.js | 16 +++ 5 files changed, 427 insertions(+) create mode 100644 _tests_/Categorias/consultarListaCategorias.controller.test.js create mode 100644 _tests_/Clientes/eliminarCliente.controller.test.js create mode 100644 _tests_/Clientes/leerCliente.controller.test.js create mode 100644 _tests_/Empleados/exportarEmpleados.controller.test.js diff --git a/_tests_/Categorias/consultarListaCategorias.controller.test.js b/_tests_/Categorias/consultarListaCategorias.controller.test.js new file mode 100644 index 00000000..c6e4b246 --- /dev/null +++ b/_tests_/Categorias/consultarListaCategorias.controller.test.js @@ -0,0 +1,89 @@ +/** + * RF[47] Consulta Lista de Categorías - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF47 + * Mocks antes de importar el controlador + */ +jest.mock('@altertex/cat/repos/repositorioConsultarListaCategorias', () => ({ + consultarListaCategorias: jest.fn(), +})); + +// Importar después del mock +const controlador = require('@altertex/cat/ctrl/consultarListaCategorias.controller'); +const repositorio = require('@altertex/cat/repos/repositorioConsultarListaCategorias'); +const MENSAJES_CATEGORIAS = require('@altertex/util/const/mensajesCategorias'); + +describe('Controlador consultarListaCategorias', () => { + let req; + let res; + + beforeEach(() => { + jest.clearAllMocks(); + + req = { + user: { + clienteSeleccionado: '3', + }, + }; + + res = { + status: jest.fn().mockReturnThis(), + json: jest.fn(), + }; + + jest.spyOn(console, 'error').mockImplementation(() => {}); // Evita logs + }); + + // Escenario 1: No se encuentran categorías + test('Debe retornar mensaje cuando no hay categorías encontradas', async () => { + repositorio.consultarListaCategorias.mockResolvedValue([]); + + await controlador.consultarListaCategorias(req, res); + + expect(repositorio.consultarListaCategorias).toHaveBeenCalledWith(3); + expect(res.status).toHaveBeenCalledWith(MENSAJES_CATEGORIAS.CATEGORIAS_NO_ENCONTRADAS.codigo); + expect(res.json).toHaveBeenCalledWith({ + mensaje: MENSAJES_CATEGORIAS.CATEGORIAS_NO_ENCONTRADAS.mensaje, + }); + }); + + // Escenario 2: Lista de categorías obtenida correctamente + test('Debe retornar la lista de categorías cuando se obtienen con éxito', async () => { + const mockCategorias = [ + { + idCategoria: 1, + nombre: 'Camisas', + productos: ['Camisa A', 'Camisa B'], + }, + { + idCategoria: 2, + nombre: 'Pantalones', + productos: ['Pantalón X'], + }, + ]; + + repositorio.consultarListaCategorias.mockResolvedValue(mockCategorias); + + await controlador.consultarListaCategorias(req, res); + + expect(repositorio.consultarListaCategorias).toHaveBeenCalledWith(3); + expect(res.status).toHaveBeenCalledWith(MENSAJES_CATEGORIAS.LISTA_CATEGORIAS_OBTENIDA.codigo); + expect(res.json).toHaveBeenCalledWith({ + mensaje: MENSAJES_CATEGORIAS.LISTA_CATEGORIAS_OBTENIDA.mensaje, + listaCategoria: mockCategorias, + }); + }); + + // Escenario 3: Error inesperado en el repositorio + test('Debe manejar errores si falla el repositorio', async () => { + repositorio.consultarListaCategorias.mockRejectedValue( + new Error('Error de base de datos') + ); + + await controlador.consultarListaCategorias(req, res); + + expect(repositorio.consultarListaCategorias).toHaveBeenCalledWith(3); + expect(res.status).toHaveBeenCalledWith(MENSAJES_CATEGORIAS.ERROR_OBTENER_CATEGORIAS.codigo); + expect(res.json).toHaveBeenCalledWith({ + mensaje: MENSAJES_CATEGORIAS.ERROR_OBTENER_CATEGORIAS.mensaje, + }); + }); +}); \ No newline at end of file diff --git a/_tests_/Clientes/eliminarCliente.controller.test.js b/_tests_/Clientes/eliminarCliente.controller.test.js new file mode 100644 index 00000000..22b47d57 --- /dev/null +++ b/_tests_/Clientes/eliminarCliente.controller.test.js @@ -0,0 +1,92 @@ +/** + * RF[15] Eliminar Cliente - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF15 + * Mocks antes de importar el controlador + */ +jest.mock('@altertex/cli/repos/repositorioEliminarCliente', () => ({ + eliminarClientePorId: jest.fn(), +})); +jest.mock('@altertex/util/ser/eliminarImagenS3', () => jest.fn()); +jest.mock('@altertex/util/ser/extraerNombreArchivoS3', () => jest.fn()); +jest.mock('@altertex/util/ser/correrQuery', () => jest.fn()); + +const controlador = require('@altertex/cli/ctrl/eliminarCliente.controller'); +const repositorio = require('@altertex/cli/repos/repositorioEliminarCliente'); +const eliminarImagenS3 = require('@altertex/util/ser/eliminarImagenS3'); +const extraerNombreArchivoS3 = require('@altertex/util/ser/extraerNombreArchivoS3'); +const correrQuery = require('@altertex/util/ser/correrQuery'); +const MENSAJES_CLIENTES = require('@altertex/util/const/mensajesClientes'); + +describe('Controlador eliminarCliente', () => { + let req; + let res; + + beforeEach(() => { + jest.clearAllMocks(); + + req = { + body: { idCliente: '3' }, + }; + + res = { + status: jest.fn().mockReturnThis(), + json: jest.fn(), + }; + + jest.spyOn(console, 'error').mockImplementation(() => {}); + }); + + // Escenario 1: ID inválido + test('Debe retornar error si el ID de cliente es inválido', async () => { + req.body.idCliente = ' '; + + await controlador.eliminarCliente(req, res); + + expect(res.status).toHaveBeenCalledWith(MENSAJES_CLIENTES.CLIENTE_INVALIDO.codigo); + expect(res.json).toHaveBeenCalledWith({ + mensaje: MENSAJES_CLIENTES.CLIENTE_INVALIDO.mensaje, + }); + }); + + // Escenario 2: Cliente no encontrado + test('Debe retornar error si el cliente no existe', async () => { + correrQuery.mockResolvedValue([]); + repositorio.eliminarClientePorId.mockResolvedValue({ affectedRows: 0 }); + + await controlador.eliminarCliente(req, res); + + expect(repositorio.eliminarClientePorId).toHaveBeenCalledWith(3); + expect(res.status).toHaveBeenCalledWith(MENSAJES_CLIENTES.CLIENTE_NO_ENCONTRADO.codigo); + expect(res.json).toHaveBeenCalledWith({ + mensaje: MENSAJES_CLIENTES.CLIENTE_NO_ENCONTRADO.mensaje, + }); + }); + + // Escenario 3: Cliente eliminado correctamente con imagen + test('Debe eliminar al cliente y su imagen si existe', async () => { + correrQuery.mockResolvedValue([{ urlImagen: 'https://bucket.s3/cliente123.jpg' }]); + extraerNombreArchivoS3.mockReturnValue('cliente123.jpg'); + repositorio.eliminarClientePorId.mockResolvedValue({ affectedRows: 1 }); + + await controlador.eliminarCliente(req, res); + + expect(correrQuery).toHaveBeenCalled(); + expect(extraerNombreArchivoS3).toHaveBeenCalledWith('https://bucket.s3/cliente123.jpg'); + expect(eliminarImagenS3).toHaveBeenCalledWith('clientes/', 'cliente123.jpg'); + expect(res.status).toHaveBeenCalledWith(MENSAJES_CLIENTES.CLIENTE_ELIMINADO.codigo); + expect(res.json).toHaveBeenCalledWith({ + mensaje: MENSAJES_CLIENTES.CLIENTE_ELIMINADO.mensaje, + }); + }); + + // Escenario 4: Error inesperado + test('Debe manejar errores inesperados en el try/catch', async () => { + correrQuery.mockRejectedValue(new Error('DB error')); + + await controlador.eliminarCliente(req, res); + + expect(res.status).toHaveBeenCalledWith(MENSAJES_CLIENTES.ERROR_ELIMINAR_CLIENTE.codigo); + expect(res.json).toHaveBeenCalledWith({ + mensaje: MENSAJES_CLIENTES.ERROR_ELIMINAR_CLIENTE.mensaje, + }); + }); +}); diff --git a/_tests_/Clientes/leerCliente.controller.test.js b/_tests_/Clientes/leerCliente.controller.test.js new file mode 100644 index 00000000..953b7318 --- /dev/null +++ b/_tests_/Clientes/leerCliente.controller.test.js @@ -0,0 +1,124 @@ +/** + * RF[13] Leer Cliente - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/rf13/ + * Mocks antes de importar el controlador + */ +jest.mock('@altertex/cli/repos/repositorioLeerCliente', () => ({ + obtenerClientePorId: jest.fn(), +})); +jest.mock('@altertex/util/ser/obtenerImagenCliente', () => jest.fn()); + +const controlador = require('@altertex/cli/ctrl/leerCliente.controller'); +const repositorio = require('@altertex/cli/repos/repositorioLeerCliente'); +const obtenerImagenCliente = require('@altertex/util/ser/obtenerImagenCliente'); +const MENSAJES_CLIENTES = require('@altertex/util/const/mensajesClientes'); + +describe('Controlador leerCliente', () => { + let req; + let res; + + beforeEach(() => { + jest.clearAllMocks(); + + req = { + body: { idCliente: '7' }, + }; + + res = { + status: jest.fn().mockReturnThis(), + json: jest.fn(), + }; + + jest.spyOn(console, 'error').mockImplementation(() => {}); + }); + + // Escenario 1: ID inválido + test('Debe retornar error si el ID es inválido', async () => { + req.body.idCliente = ' '; + + await controlador.leerCliente(req, res); + + expect(res.status).toHaveBeenCalledWith(MENSAJES_CLIENTES.PARAMETROS_INVALIDOS.codigo); + expect(res.json).toHaveBeenCalledWith({ + mensaje: MENSAJES_CLIENTES.PARAMETROS_INVALIDOS.mensaje, + }); + }); + + // Escenario 2: Cliente no encontrado + test('Debe retornar error si el cliente no existe', async () => { + repositorio.obtenerClientePorId.mockResolvedValue(null); + + await controlador.leerCliente(req, res); + + expect(res.status).toHaveBeenCalledWith(MENSAJES_CLIENTES.CLIENTE_NO_ENCONTRADO.codigo); + expect(res.json).toHaveBeenCalledWith({ + mensaje: MENSAJES_CLIENTES.CLIENTE_NO_ENCONTRADO.mensaje, + }); + }); + + // Escenario 3: Cliente encontrado con imagen exitosa + test('Debe retornar cliente completo con imagen si todo funciona bien', async () => { + const mockCliente = { + idCliente: 7, + nombreLegal: 'Tech S.A.', + nombreVisible: 'Tech Store', + empleados: [], + usuariosAsignados: [], + numeroEmpleados: 10, + urlImagen: 'https://bucket.s3/cliente.jpg', + }; + + repositorio.obtenerClientePorId.mockResolvedValue(mockCliente); + obtenerImagenCliente.mockResolvedValue('https://signed-url.com/cliente.jpg'); + + await controlador.leerCliente(req, res); + + expect(obtenerImagenCliente).toHaveBeenCalledWith(mockCliente.urlImagen); + expect(res.status).toHaveBeenCalledWith(MENSAJES_CLIENTES.CONSULTA_EXITOSA.codigo); + expect(res.json).toHaveBeenCalledWith({ + mensaje: MENSAJES_CLIENTES.CONSULTA_EXITOSA.mensaje, + cliente: { + ...mockCliente, + imagenCliente: 'https://signed-url.com/cliente.jpg', + }, + }); + }); + + // Escenario 4: Cliente encontrado, pero falla obtenerImagenCliente + test('Debe retornar imagen placeholder si obtenerImagenCliente falla', async () => { + const mockCliente = { + idCliente: 7, + nombreLegal: 'Tech S.A.', + nombreVisible: 'Tech Store', + empleados: [], + usuariosAsignados: [], + numeroEmpleados: 10, + urlImagen: 'https://bucket.s3/cliente.jpg', + }; + + repositorio.obtenerClientePorId.mockResolvedValue(mockCliente); + obtenerImagenCliente.mockRejectedValue(new Error('Fallo S3')); + + await controlador.leerCliente(req, res); + + expect(obtenerImagenCliente).toHaveBeenCalledWith(mockCliente.urlImagen); + expect(res.json).toHaveBeenCalledWith({ + mensaje: MENSAJES_CLIENTES.CONSULTA_EXITOSA.mensaje, + cliente: { + ...mockCliente, + imagenCliente: '/placeholder.png', + }, + }); + }); + + // Escenario 5: Error inesperado + test('Debe manejar error inesperado del repositorio', async () => { + repositorio.obtenerClientePorId.mockRejectedValue(new Error('DB error')); + + await controlador.leerCliente(req, res); + + expect(res.status).toHaveBeenCalledWith(MENSAJES_CLIENTES.ERROR_CONSULTAR_CLIENTE.codigo); + expect(res.json).toHaveBeenCalledWith({ + mensaje: MENSAJES_CLIENTES.ERROR_CONSULTAR_CLIENTE.mensaje, + }); + }); +}); \ No newline at end of file diff --git a/_tests_/Empleados/exportarEmpleados.controller.test.js b/_tests_/Empleados/exportarEmpleados.controller.test.js new file mode 100644 index 00000000..3dd61936 --- /dev/null +++ b/_tests_/Empleados/exportarEmpleados.controller.test.js @@ -0,0 +1,106 @@ +/** + * RF[59] Exportar Empleados - https://codeandco-wiki.netlify.app/docs/next/proyectos/textiles/documentacion/requisitos/RF59 + * Mocks antes de importar el controlador + */ +jest.mock('@altertex/emp/repos/repositorioExportarEmpleado', () => ({ + obtenerEmpleadosExportacion: jest.fn(), +})); + +const controlador = require('@altertex/emp/ctrl/exportarEmpleados.controller'); +const repositorio = require('@altertex/emp/repos/repositorioExportarEmpleado'); +const MENSAJES_EMPLEADOS = require('@altertex/util/const/mensajesEmpleados'); + +describe('Controlador exportarEmpleados', () => { + let req; + let res; + + beforeEach(() => { + jest.clearAllMocks(); + + req = { + user: { clienteSeleccionado: '5' }, + body: { idsEmpleado: [1, 2] }, + }; + + res = { + status: jest.fn().mockReturnThis(), + json: jest.fn(), + }; + + jest.spyOn(console, 'error').mockImplementation(() => {}); + }); + + // Escenario 1: No se envían IDs + test('Debe retornar error si no se envían empleados a exportar', async () => { + req.body.idsEmpleado = []; + + await controlador.exportarEmpleados(req, res); + + expect(res.status).toHaveBeenCalledWith(MENSAJES_EMPLEADOS.PARAMETROS_INVALIDOS.codigo); + expect(res.json).toHaveBeenCalledWith({ + mensaje: 'Debes seleccionar al menos un empleado para exportar.', + }); + }); + + // Escenario 2: No se encuentran empleados en la base + test('Debe retornar mensaje si no hay empleados encontrados', async () => { + repositorio.obtenerEmpleadosExportacion.mockResolvedValue([]); + + await controlador.exportarEmpleados(req, res); + + expect(repositorio.obtenerEmpleadosExportacion).toHaveBeenCalledWith(5, [1, 2]); + expect(res.status).toHaveBeenCalledWith(MENSAJES_EMPLEADOS.EMPLEADOS_NO_ENCONTRADOS.codigo); + expect(res.json).toHaveBeenCalledWith({ + mensaje: MENSAJES_EMPLEADOS.EMPLEADOS_NO_ENCONTRADOS.mensaje, + csv: '', + }); + }); + + // Escenario 3: Exportación exitosa + test('Debe retornar CSV con empleados exportados correctamente', async () => { + const empleadosMock = [ + { + idEmpleado: 1, + nombreCompleto: 'Juan Pérez', + correoElectronico: 'juan@example.com', + numeroTelefono: '1234567890', + direccion: 'Calle Falsa 123', + fechaNacimiento: '1990-01-01', + genero: 'M', + estatus: 'Activo', + numeroEmergencia: '0987654321', + areaTrabajo: 'Ventas', + posicion: 'Ejecutivo', + cantidadPuntos: 200, + antiguedad: '2020-01-01', + }, + ]; + + repositorio.obtenerEmpleadosExportacion.mockResolvedValue(empleadosMock); + + await controlador.exportarEmpleados(req, res); + + expect(res.status).toHaveBeenCalledWith(MENSAJES_EMPLEADOS.LISTA_EMPLEADOS_EXPORTADA.codigo); + const csvLlamado = res.json.mock.calls[0][0].csv; + + expect(res.json).toHaveBeenCalledWith({ + mensaje: MENSAJES_EMPLEADOS.LISTA_EMPLEADOS_EXPORTADA.mensaje, + csv: expect.stringContaining('Juan Pérez'), // Verifica contenido parcial + }); + + expect(csvLlamado.startsWith('\uFEFF')).toBe(true); // CSV debe tener BOM + }); + + // Escenario 4: Error inesperado + test('Debe manejar errores del repositorio', async () => { + repositorio.obtenerEmpleadosExportacion.mockRejectedValue(new Error('Error de DB')); + + await controlador.exportarEmpleados(req, res); + + expect(res.status).toHaveBeenCalledWith(MENSAJES_EMPLEADOS.ERROR_EXPORTAR_EMPLEADOS.codigo); + expect(res.json).toHaveBeenCalledWith({ + mensaje: MENSAJES_EMPLEADOS.ERROR_EXPORTAR_EMPLEADOS.mensaje, + csv: '', + }); + }); +}); \ No newline at end of file diff --git a/jest.config.js b/jest.config.js index 6466bef9..e9ea70e1 100644 --- a/jest.config.js +++ b/jest.config.js @@ -30,6 +30,22 @@ module.exports = { '^@altertex/aut/datos/(.*)$': '/Autenticacion/Datos/$1', '^@altertex/aut/(.*)$': '/Autenticacion/$1', + // Categorias module mappings + "^@altertex/cat/ctrl/(.*)$": "/Categorias/Controladores/$1", + "^@altertex/cat/repos/(.*)$": "/Categorias/Datos/Repositorios/$1", + "^@altertex/cat/rutasInd/(.*)$": "/Categorias/Rutas/RutasIndividuales/$1", + "^@altertex/cat/rutas/(.*)$": "/Categorias/Rutas/$1", + "^@altertex/cat/datos/(.*)$": "/Categorias/Datos/$1", + "^@altertex/cat/(.*)$": "/Categorias/$1", + + // Clientes module mappings + "^@altertex/cli/ctrl/(.*)$": "/Clientes/Controladores/$1", + "^@altertex/cli/repos/(.*)$": "/Clientes/Datos/Repositorios/$1", + "^@altertex/cli/rutasInd/(.*)$": "/Clientes/Rutas/RutasIndividuales/$1", + "^@altertex/cli/rutas/(.*)$": "/Clientes/Rutas/$1", + "^@altertex/cli/datos/(.*)$": "/Clientes/Datos/$1", + "^@altertex/cli/(.*)$": "/Clientes/$1", + // Cuotas module mappings (added) '^@altertex/cuota/ctrl/(.*)$': '/Cuotas/Controladores/$1', '^@altertex/cuota/repos/(.*)$': '/Cuotas/Datos/Repositorios/$1', From 1ce82c1627fcc29ffdb345ff38353d5b8a31025e Mon Sep 17 00:00:00 2001 From: max Date: Fri, 6 Jun 2025 15:30:52 -0600 Subject: [PATCH 099/116] fix(usuarios): improve error handling in actualizarUsuario and clarify idCliente alias in consultasUsuarios --- Usuarios/Controladores/actualizarUsuario.controller.js | 2 +- Utilidades/Constantes/consultasUsuarios.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Usuarios/Controladores/actualizarUsuario.controller.js b/Usuarios/Controladores/actualizarUsuario.controller.js index 16a60a44..f4159747 100644 --- a/Usuarios/Controladores/actualizarUsuario.controller.js +++ b/Usuarios/Controladores/actualizarUsuario.controller.js @@ -46,7 +46,7 @@ exports.actualizarUsuario = async (req, res) => { return res .status(MENSAJES.USUARIO_ACTUALIZADO.codigo) .json({ mensaje: MENSAJES.USUARIO_ACTUALIZADO.mensaje, datos }); - } catch { + } catch (e) { return res .status(MENSAJES.ERROR_ACTUALIZAR_USUARIO.codigo) .json({ mensaje: MENSAJES.ERROR_ACTUALIZAR_USUARIO.mensaje }); diff --git a/Utilidades/Constantes/consultasUsuarios.js b/Utilidades/Constantes/consultasUsuarios.js index f263f80a..bbf590d5 100644 --- a/Utilidades/Constantes/consultasUsuarios.js +++ b/Utilidades/Constantes/consultasUsuarios.js @@ -60,7 +60,7 @@ module.exports = { u.genero, u.estatus, r.idRol AS rol, - uc.idCliente, + uc.idCliente AS idCliente, c.nombreComercial AS nombreCliente FROM usuario u LEFT JOIN usuario_rol ur ON u.idUsuario = ur.idUsuario From 4604ef59184a92daa30f9508a3f7a8887eac1c15 Mon Sep 17 00:00:00 2001 From: ArturoSanRod Date: Fri, 6 Jun 2025 16:04:20 -0600 Subject: [PATCH 100/116] Implementacion de actualizar set de cuotas --- .../actualizarSetCuotas.controller.js | 8 +-- .../actualizarSetCuotasRepositorio.js | 49 ++++++------------- .../Repositorios/leerSetCuotasRepositorio.js | 33 ++++++++++++- 3 files changed, 52 insertions(+), 38 deletions(-) diff --git a/Cuotas/Controladores/actualizarSetCuotas.controller.js b/Cuotas/Controladores/actualizarSetCuotas.controller.js index 72422ec6..248499bc 100644 --- a/Cuotas/Controladores/actualizarSetCuotas.controller.js +++ b/Cuotas/Controladores/actualizarSetCuotas.controller.js @@ -1,4 +1,4 @@ -const MENSAJES_CUOTAS = require('@altertex/util/const/mensajesCuotas'); +// actualizarSetCuotas.controller.js const repositorio = require('@altertex/cuota/repos/actualizarSetCuotasRepositorio'); exports.actualizarSetCuotas = async (req, res) => { @@ -6,14 +6,14 @@ exports.actualizarSetCuotas = async (req, res) => { const { idCuotaSet, cambios } = req.body; if (!idCuotaSet || !cambios) { - return res.status(400).json({ mensaje: MENSAJES_CUOTAS.PARAMETROS_INVALIDOS.mensaje }); + return res.status(400).json({ mensaje: 'Datos incompletos' }); } await repositorio.actualizarSetCuotas(idCuotaSet, cambios); + return res.status(200).json({ mensaje: 'Set de cuotas actualizado correctamente' }); - return res.status(200).json({ mensaje: MENSAJES_CUOTAS.ACTUALIZACION_EXITOSA.mensaje }); } catch (error) { console.error('[ERROR] actualizarSetCuotas:', error); - return res.status(500).json({ mensaje: error.message || MENSAJES_CUOTAS.ERROR_ACTUALIZACION.mensaje }); + return res.status(500).json({ mensaje: error.message }); } }; \ No newline at end of file diff --git a/Cuotas/Datos/Repositorios/actualizarSetCuotasRepositorio.js b/Cuotas/Datos/Repositorios/actualizarSetCuotasRepositorio.js index 90692923..f87dcb58 100644 --- a/Cuotas/Datos/Repositorios/actualizarSetCuotasRepositorio.js +++ b/Cuotas/Datos/Repositorios/actualizarSetCuotasRepositorio.js @@ -1,49 +1,32 @@ -const db = require('@altertex/util/bd/db'); +// actualizarSetCuotasRepositorio.js const CONSULTAS_CUOTAS = require('@altertex/util/const/consultasCuotas'); const correrQuery = require('@altertex/util/ser/correrQuery'); -const MENSAJES = require('@altertex/util/const/mensajesCuotas'); - exports.actualizarSetCuotas = async (idCuotaSet, cambios) => { - const { - nombre, - descripcion, - periodoRenovacion, - renovacionHabilitada, - productos = [] - } = cambios; + const { nombre, descripcion, periodoRenovacion, renovacionHabilitada, productos = [] } = cambios; - console.log('[DEBUG] Iniciando actualización del set de cuotas', { idCuotaSet, cambios }); - - // Actualizar cuota_set - const resultado = await correrQuery(CONSULTAS_CUOTAS.ACTUALIZAR_CUOTA_SET, [ + // 1. Actualizar datos básicos de la cuota + await correrQuery(CONSULTAS_CUOTAS.ACTUALIZAR_CUOTA_SET, [ nombre, descripcion, periodoRenovacion, renovacionHabilitada, - new Date(), // ultimaActualizacion + new Date(), idCuotaSet ]); - console.log('[DEBUG] Resultado UPDATE cuota_set:', resultado); - - if (!resultado || resultado.affectedRows === 0) { - throw new Error(MENSAJES.ERROR_ACTUALIZACION.mensaje); - } - - // Eliminar productos anteriores + // 2. Eliminar productos anteriores await correrQuery(CONSULTAS_CUOTAS.ELIMINAR_PRODUCTOS_CUOTA_SET, [idCuotaSet]); - // Insertar nuevos productos + // 3. Insertar nuevos productos (solo los que tienen ID válido) for (const producto of productos) { - const { idProducto, limite, limiteActual } = producto; - await correrQuery(CONSULTAS_CUOTAS.INSERTAR_CUOTA_PRODUCTO_ACTUALIZAR, [ - idCuotaSet, - idProducto, - limite, - limiteActual - ]); + if (producto.idProducto && producto.idProducto > 0) { + await correrQuery(CONSULTAS_CUOTAS.INSERTAR_CUOTA_PRODUCTO_ACTUALIZAR, [ + idCuotaSet, + producto.idProducto, + producto.limite || 0, + producto.limiteActual || 0 + ]); + } } - - console.log('[DEBUG] Set de cuotas actualizado exitosamente.'); -}; +}; \ No newline at end of file diff --git a/Cuotas/Datos/Repositorios/leerSetCuotasRepositorio.js b/Cuotas/Datos/Repositorios/leerSetCuotasRepositorio.js index e5831468..a7a57dc3 100644 --- a/Cuotas/Datos/Repositorios/leerSetCuotasRepositorio.js +++ b/Cuotas/Datos/Repositorios/leerSetCuotasRepositorio.js @@ -11,27 +11,58 @@ const CONSULTAS_CUOTAS = require('@altertex/util/const/consultasCuotas'); * @throws {Error} Si ocurre un error al ejecutar la consulta. */ exports.obtenerSetCuotaPorId = async (idSetCuota) => { + console.log('[DEBUG] Consultando set de cuotas con ID:', idSetCuota); + const query = CONSULTAS_CUOTAS.LEER_CUOTA_SET; const queryCuotas = CONSULTAS_CUOTAS.LEER_CUOTA_SET_PRODUCTOS; const resultado = await correrQuery(query, [idSetCuota]); + console.log('[DEBUG] Resultado cuota_set:', resultado); + if (resultado.length === 0) return null; const productosCuota = await correrQuery(queryCuotas, [idSetCuota]); + console.log('[DEBUG] Resultado productos cuota:', productosCuota); + + // 🔥 CORREGIDO: Devolver los productos con toda su información const productos = productosCuota.map((producto) => ({ + idProducto: producto.idProducto, // ✅ Incluir el ID del producto nombre: producto.nombreComun, + nombreComun: producto.nombreComun, + cuota_valor: producto.cuota_valor, + limite_actual: producto.limite_actual, + // Campos alternativos para compatibilidad + valor: producto.cuota_valor, + limite: producto.cuota_valor, + limiteActual: producto.limite_actual })); + + // 🔥 MANTENER ESTO PARA RETROCOMPATIBILIDAD const cuotas = productosCuota.map((producto) => ({ valor: producto.cuota_valor, })); + // ✅ CORREGIDO: Incluir TODOS los campos de la cuota const setCuota = { + // IDs en ambos formatos para compatibilidad idSetCuota: resultado[0].idCuotaSet, + idCuotaSet: resultado[0].idCuotaSet, + + // Información básica nombre: resultado[0].nombre, descripcion: resultado[0].descripcion, + + // 🔥 CAMPOS QUE FALTABAN: + periodoRenovacion: resultado[0].periodoRenovacion, + renovacionHabilitada: resultado[0].renovacionHabilitada, + ultimaActualizacion: resultado[0].ultimaActualizacion, + + // Productos y cuotas productos, cuotas, }; + console.log('[DEBUG] Set de cuotas completo a devolver:', JSON.stringify(setCuota, null, 2)); + return setCuota; -}; +}; \ No newline at end of file From 9c24ca49ff87c3441207d65b1a610a739dd0037c Mon Sep 17 00:00:00 2001 From: ArturoSanRod Date: Fri, 6 Jun 2025 16:16:43 -0600 Subject: [PATCH 101/116] Fix de implementacion de console logs --- .../actualizarSetCuotas.controller.js | 1 - .../Repositorios/leerSetCuotasRepositorio.js | 15 +-------------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/Cuotas/Controladores/actualizarSetCuotas.controller.js b/Cuotas/Controladores/actualizarSetCuotas.controller.js index 248499bc..30e1600c 100644 --- a/Cuotas/Controladores/actualizarSetCuotas.controller.js +++ b/Cuotas/Controladores/actualizarSetCuotas.controller.js @@ -13,7 +13,6 @@ exports.actualizarSetCuotas = async (req, res) => { return res.status(200).json({ mensaje: 'Set de cuotas actualizado correctamente' }); } catch (error) { - console.error('[ERROR] actualizarSetCuotas:', error); return res.status(500).json({ mensaje: error.message }); } }; \ No newline at end of file diff --git a/Cuotas/Datos/Repositorios/leerSetCuotasRepositorio.js b/Cuotas/Datos/Repositorios/leerSetCuotasRepositorio.js index a7a57dc3..b939a7b6 100644 --- a/Cuotas/Datos/Repositorios/leerSetCuotasRepositorio.js +++ b/Cuotas/Datos/Repositorios/leerSetCuotasRepositorio.js @@ -11,58 +11,45 @@ const CONSULTAS_CUOTAS = require('@altertex/util/const/consultasCuotas'); * @throws {Error} Si ocurre un error al ejecutar la consulta. */ exports.obtenerSetCuotaPorId = async (idSetCuota) => { - console.log('[DEBUG] Consultando set de cuotas con ID:', idSetCuota); const query = CONSULTAS_CUOTAS.LEER_CUOTA_SET; const queryCuotas = CONSULTAS_CUOTAS.LEER_CUOTA_SET_PRODUCTOS; const resultado = await correrQuery(query, [idSetCuota]); - console.log('[DEBUG] Resultado cuota_set:', resultado); if (resultado.length === 0) return null; const productosCuota = await correrQuery(queryCuotas, [idSetCuota]); - console.log('[DEBUG] Resultado productos cuota:', productosCuota); - // 🔥 CORREGIDO: Devolver los productos con toda su información const productos = productosCuota.map((producto) => ({ - idProducto: producto.idProducto, // ✅ Incluir el ID del producto + idProducto: producto.idProducto, nombre: producto.nombreComun, nombreComun: producto.nombreComun, cuota_valor: producto.cuota_valor, limite_actual: producto.limite_actual, - // Campos alternativos para compatibilidad valor: producto.cuota_valor, limite: producto.cuota_valor, limiteActual: producto.limite_actual })); - // 🔥 MANTENER ESTO PARA RETROCOMPATIBILIDAD const cuotas = productosCuota.map((producto) => ({ valor: producto.cuota_valor, })); - // ✅ CORREGIDO: Incluir TODOS los campos de la cuota const setCuota = { - // IDs en ambos formatos para compatibilidad idSetCuota: resultado[0].idCuotaSet, idCuotaSet: resultado[0].idCuotaSet, - // Información básica nombre: resultado[0].nombre, descripcion: resultado[0].descripcion, - // 🔥 CAMPOS QUE FALTABAN: periodoRenovacion: resultado[0].periodoRenovacion, renovacionHabilitada: resultado[0].renovacionHabilitada, ultimaActualizacion: resultado[0].ultimaActualizacion, - - // Productos y cuotas productos, cuotas, }; - console.log('[DEBUG] Set de cuotas completo a devolver:', JSON.stringify(setCuota, null, 2)); return setCuota; }; \ No newline at end of file From d9ee1e9f9dadb9f7704fe3396a9c934d92adc7f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valeria=20Zu=C3=B1iga=20Mendoza?= Date: Fri, 6 Jun 2025 16:27:17 -0600 Subject: [PATCH 102/116] fix: Agregar manejor de error de nombre duplicado --- .../actualizarSetsProductos.controller.js | 6 +++--- .../repositorioActualizarSetsProductos.js | 11 ++++++++++- Utilidades/Constantes/consultasSetsProductos.js | 6 ++++++ Utilidades/Constantes/mensajesSetsProductos.js | 4 ++++ 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/SetsProductos/Controladores/actualizarSetsProductos.controller.js b/SetsProductos/Controladores/actualizarSetsProductos.controller.js index 3ad09efe..ae6d0fcf 100644 --- a/SetsProductos/Controladores/actualizarSetsProductos.controller.js +++ b/SetsProductos/Controladores/actualizarSetsProductos.controller.js @@ -21,6 +21,7 @@ const repositorio = require('@altertex/setspro/repos/repositorioActualizarSetsPr */ exports.actualizarSetProductos = async (req, res) => { const datosActualizacion = req.body; + const cliente = req.user.clienteSeleccionado; // Validación básica de datos requeridos if ( @@ -36,7 +37,7 @@ exports.actualizarSetProductos = async (req, res) => { } try { - await repositorio.actualizarSetProductos(datosActualizacion); + await repositorio.actualizarSetProductos(cliente, datosActualizacion); return res.status(MENSAJES.SET_ACTUALIZADO.codigo).json({ mensaje: MENSAJES.SET_ACTUALIZADO.mensaje, @@ -46,8 +47,7 @@ exports.actualizarSetProductos = async (req, res) => { console.error('Error al actualizar set de productos:', error); return res.status(MENSAJES.ERROR_ACTUALIZAR_SET.codigo).json({ - mensaje: MENSAJES.ERROR_ACTUALIZAR_SET.mensaje, - error: error.message, + mensaje: error.message, }); } }; diff --git a/SetsProductos/Datos/Repositorios/repositorioActualizarSetsProductos.js b/SetsProductos/Datos/Repositorios/repositorioActualizarSetsProductos.js index 51577777..33ea62ff 100644 --- a/SetsProductos/Datos/Repositorios/repositorioActualizarSetsProductos.js +++ b/SetsProductos/Datos/Repositorios/repositorioActualizarSetsProductos.js @@ -15,10 +15,19 @@ const CONSULTAS = require('@altertex/util/const/consultasSetsProductos'); * @param {boolean} datos.activo - Estado activo/inactivo. * @param {number[]} datos.productos - Lista de IDs de productos a asociar. Array vacío elimina todas las asociaciones. */ -exports.actualizarSetProductos = async (datos) => { +exports.actualizarSetProductos = async (idCliente, datos) => { const { idSetProducto, nombre, descripcion, activo, productos } = datos; try { + + const duplicados = await correrQuery(CONSULTAS.CONSULTAR_NOMBRE_DUPLICADO, [ + idCliente, + nombre, + ]); + + if (duplicados.length > 0) { + throw new Error(MENSAJES.ERROR_NOMBRE_NORMAL_DUPLICADO.mensaje); + } // 1. Actualizar info básica del set await correrQuery(CONSULTAS.ACTUALIZAR_SET_INFO, [nombre, descripcion, activo, idSetProducto]); diff --git a/Utilidades/Constantes/consultasSetsProductos.js b/Utilidades/Constantes/consultasSetsProductos.js index adf15222..a3457098 100644 --- a/Utilidades/Constantes/consultasSetsProductos.js +++ b/Utilidades/Constantes/consultasSetsProductos.js @@ -44,6 +44,12 @@ module.exports = { WHERE idCliente = ? AND (nombre = ? OR nombreVisible = ?); `, + CONSULTAR_NOMBRE_DUPLICADO: ` + SELECT idSetProducto + FROM set_producto + WHERE idCliente = ? + AND (nombre = ?); + `, CONSULTAR_PRODUCTOS_EXISTENTES: ` SELECT idProducto FROM producto diff --git a/Utilidades/Constantes/mensajesSetsProductos.js b/Utilidades/Constantes/mensajesSetsProductos.js index 0d832da9..a2333e04 100644 --- a/Utilidades/Constantes/mensajesSetsProductos.js +++ b/Utilidades/Constantes/mensajesSetsProductos.js @@ -56,6 +56,10 @@ module.exports = { codigo: 400, mensaje: 'Nombre o nombre visible duplicado.', }, + ERROR_NOMBRE_NORMAL_DUPLICADO: { + codigo: 400, + mensaje: 'Nombre duplicado.', + }, ERROR_PRODUCTOS_INVALIDOS: { codigo: 400, mensaje: 'Uno o más productos no existen en este cliente.', From 08a6c838a142ac7c3a82f9ac67a00a04baa48c10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valeria=20Zu=C3=B1iga=20Mendoza?= Date: Fri, 6 Jun 2025 16:34:59 -0600 Subject: [PATCH 103/116] docs: Agregar comentarios de JSDocs --- .../repositorioActualizarSetsProductos.js | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/SetsProductos/Datos/Repositorios/repositorioActualizarSetsProductos.js b/SetsProductos/Datos/Repositorios/repositorioActualizarSetsProductos.js index 33ea62ff..9a902205 100644 --- a/SetsProductos/Datos/Repositorios/repositorioActualizarSetsProductos.js +++ b/SetsProductos/Datos/Repositorios/repositorioActualizarSetsProductos.js @@ -4,16 +4,29 @@ const CONSULTAS = require('@altertex/util/const/consultasSetsProductos'); // RF[44] Actualizar set de productos - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF44] +// RF[44] Actualizar set de productos - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF44] + /** * Actualiza un set de productos con su información general y productos asociados. + * + * - Verifica que el nuevo nombre no esté duplicado para el cliente. + * - Actualiza los campos básicos del set: nombre, descripción y estado activo. + * - Si se especifica un arreglo de productos: + * - Elimina las asociaciones que ya no existen. + * - Agrega las nuevas asociaciones. + * - Si el arreglo está vacío, elimina todas las asociaciones. * - * @param {object} datos - Datos para actualizar el set. - * @param {number} datos.idSetProducto - ID del set de productos. - * @param {number} datos.idCliente - ID del cliente. - * @param {string} datos.nombre - Nombre interno. - * @param {string} datos.descripcion - Descripción. - * @param {boolean} datos.activo - Estado activo/inactivo. - * @param {number[]} datos.productos - Lista de IDs de productos a asociar. Array vacío elimina todas las asociaciones. + * @async + * @function actualizarSetProductos + * @param {number} idCliente - ID del cliente propietario del set. + * @param {object} datos - Objeto con los datos para actualizar el set. + * @param {number} datos.idSetProducto - ID del set de productos a actualizar. + * @param {string} datos.nombre - Nombre interno del set. + * @param {string} datos.descripcion - Descripción del set. + * @param {boolean} datos.activo - Estado activo o inactivo del set. + * @param {number[]} datos.productos - Lista de IDs de productos asociados al set. + * Si es un arreglo vacío, se eliminarán todas las asociaciones. + * @throws {Error} Si ocurre un error durante la actualización o si el nombre está duplicado. */ exports.actualizarSetProductos = async (idCliente, datos) => { const { idSetProducto, nombre, descripcion, activo, productos } = datos; From d11a19f954497aeebb6bdcdd95063104e9855131 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valeria=20Zu=C3=B1iga=20Mendoza?= Date: Fri, 6 Jun 2025 16:42:45 -0600 Subject: [PATCH 104/116] fix: manjeo de error --- .../Datos/Repositorios/repositorioActualizarSetsProductos.js | 1 + Utilidades/Constantes/consultasSetsProductos.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/SetsProductos/Datos/Repositorios/repositorioActualizarSetsProductos.js b/SetsProductos/Datos/Repositorios/repositorioActualizarSetsProductos.js index 9a902205..0e91e086 100644 --- a/SetsProductos/Datos/Repositorios/repositorioActualizarSetsProductos.js +++ b/SetsProductos/Datos/Repositorios/repositorioActualizarSetsProductos.js @@ -35,6 +35,7 @@ exports.actualizarSetProductos = async (idCliente, datos) => { const duplicados = await correrQuery(CONSULTAS.CONSULTAR_NOMBRE_DUPLICADO, [ idCliente, + idSetProducto, nombre, ]); diff --git a/Utilidades/Constantes/consultasSetsProductos.js b/Utilidades/Constantes/consultasSetsProductos.js index a3457098..07a20c80 100644 --- a/Utilidades/Constantes/consultasSetsProductos.js +++ b/Utilidades/Constantes/consultasSetsProductos.js @@ -47,7 +47,7 @@ module.exports = { CONSULTAR_NOMBRE_DUPLICADO: ` SELECT idSetProducto FROM set_producto - WHERE idCliente = ? + WHERE idCliente = ? and idSetProducto!= ? AND (nombre = ?); `, CONSULTAR_PRODUCTOS_EXISTENTES: ` From dfd2f9b7acef9c692e7db83c96ec4c9131750501 Mon Sep 17 00:00:00 2001 From: angieriosc Date: Fri, 6 Jun 2025 16:53:32 -0600 Subject: [PATCH 105/116] Fix: Correciones eslint --- .../actualizarPedido.controller.js | 13 ++++++++-- .../repositorioActualizarPedido.js | 25 +++++++++++++++++-- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/Pedidos/Controladores/actualizarPedido.controller.js b/Pedidos/Controladores/actualizarPedido.controller.js index 072dbf73..299ddbd4 100644 --- a/Pedidos/Controladores/actualizarPedido.controller.js +++ b/Pedidos/Controladores/actualizarPedido.controller.js @@ -2,12 +2,21 @@ const MENSAJES = require('@altertex/util/const/mensajesPedidos'); const repositorio = require('@altertex/pedidos/repos/repositorioActualizarPedido'); /** - * RF[62] - Actualizar Pedido + * RF[62] - Actualizar Pedido [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF62] * Controlador para actualizar la información de uno o varios pedidos. + * + * Respuestas posibles: + * - 400 si faltan datos en el cuerpo de la solicitud. + * - 200 si el pedido se actualiza correctamente. + * + * @async + * @function actualizarGrupoEmpleados + * @param {Express.Request} req - Objeto de solicitud HTTP de Express. + * @param {Express.Response} res - Objeto de respuesta HTTP de Express. + * @returns {Promise} La respuesta HTTP con el estado y mensaje correspondiente. */ exports.actualizarPedido = async (req, res) => { let datos; - console.log('body', req.body); if (req.body.idPedido) { datos = [req.body]; } else if (req.body.cambios) { diff --git a/Pedidos/Datos/Repositorios/repositorioActualizarPedido.js b/Pedidos/Datos/Repositorios/repositorioActualizarPedido.js index b08156ef..002d898a 100644 --- a/Pedidos/Datos/Repositorios/repositorioActualizarPedido.js +++ b/Pedidos/Datos/Repositorios/repositorioActualizarPedido.js @@ -2,9 +2,30 @@ const correrQuery = require('@altertex/util/ser/correrQuery'); const MENSAJES = require('@altertex/util/const/mensajesPedidos'); const CONSULTAS = require('@altertex/util/const/consultasPedidos'); +//RF[62] - Actualizar Pedido - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF62] + /** - * RF[62] - Actualizar Pedido - * Repositorio para actualizar los datos de uno o varios pedidos. + * Actualiza uno o varios pedidos en la base de datos con nueva información + * de estado, precio total, ID de envío y ID de pago. + * + * Esta función realiza: + * - La validación de que se proporcionen datos para actualizar. + * - La actualización simultánea de múltiples pedidos usando Promise.all. + * - El manejo de errores durante el proceso de actualización. + * + * La función procesa cada pedido de forma paralela, actualizando sus campos + * mediante una consulta SQL preparada con los parámetros proporcionados. + * + * @async + * @function actualizarPedido + * @param {object[]} datos - Array de objetos con los datos de los pedidos a actualizar. + * @param {number} datos[].idPedido - ID único del pedido a actualizar. + * @param {string|number} datos[].estado - Nuevo estado del pedido. + * @param {number} datos[].precioTotal - Nuevo precio total del pedido. + * @param {number} datos[].idEnvio - ID del envío asociado al pedido. + * @param {number} datos[].idPago - ID del pago asociado al pedido. + * @throws {Error} 'Sin datos para actualizar.' - Si el array está vacío o no es válido. + * @throws {Error} Mensaje de error específico desde MENSAJES.ERROR_ACTUALIZAR_PEDIDO - Si ocurre algún error durante la actualización. */ exports.actualizarPedido = async (datos) => { if (!Array.isArray(datos) || datos.length === 0) { From 57b6dbd0f36581aa99d7b7e8ab94f5fd98b79e60 Mon Sep 17 00:00:00 2001 From: max Date: Fri, 6 Jun 2025 17:47:31 -0600 Subject: [PATCH 106/116] fix(package): add license information for csurf package --- package-lock.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package-lock.json b/package-lock.json index de61bb78..7420e567 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6133,6 +6133,7 @@ "resolved": "https://registry.npmjs.org/csurf/-/csurf-1.11.0.tgz", "integrity": "sha512-UCtehyEExKTxgiu8UHdGvHj4tnpE/Qctue03Giq5gPgMQ9cg/ciod5blZQ5a4uCEenNQjxyGuzygLdKUmee/bQ==", "deprecated": "This package is archived and no longer maintained. For support, visit https://github.com/expressjs/express/discussions", + "license": "MIT", "dependencies": { "cookie": "0.4.0", "cookie-signature": "1.0.6", From 4a0aae0c626abf83372a3c4415674c170aedfeb0 Mon Sep 17 00:00:00 2001 From: ArturoSanRod Date: Fri, 6 Jun 2025 19:34:32 -0600 Subject: [PATCH 107/116] Implementacion de JSDocs --- .../actualizarSetCuotas.controller.js | 10 +++++++-- .../actualizarSetCuotasRepositorio.js | 22 ++++++++++++++----- .../actualizarSetCuotas.routes.js | 18 +++++++++++++++ 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/Cuotas/Controladores/actualizarSetCuotas.controller.js b/Cuotas/Controladores/actualizarSetCuotas.controller.js index 30e1600c..7c5ae1da 100644 --- a/Cuotas/Controladores/actualizarSetCuotas.controller.js +++ b/Cuotas/Controladores/actualizarSetCuotas.controller.js @@ -1,6 +1,12 @@ -// actualizarSetCuotas.controller.js const repositorio = require('@altertex/cuota/repos/actualizarSetCuotasRepositorio'); +/** + * Controlador que gestiona la actualización de un set de cuotas. + * + * @param {object} req - Objeto de solicitud HTTP de Express. + * @param {object} res - Objeto de respuesta HTTP de Express. + * @returns {object} Respuesta JSON con el resultado de la operación. + */ exports.actualizarSetCuotas = async (req, res) => { try { const { idCuotaSet, cambios } = req.body; @@ -15,4 +21,4 @@ exports.actualizarSetCuotas = async (req, res) => { } catch (error) { return res.status(500).json({ mensaje: error.message }); } -}; \ No newline at end of file +}; diff --git a/Cuotas/Datos/Repositorios/actualizarSetCuotasRepositorio.js b/Cuotas/Datos/Repositorios/actualizarSetCuotasRepositorio.js index f87dcb58..158b6a66 100644 --- a/Cuotas/Datos/Repositorios/actualizarSetCuotasRepositorio.js +++ b/Cuotas/Datos/Repositorios/actualizarSetCuotasRepositorio.js @@ -1,11 +1,25 @@ -// actualizarSetCuotasRepositorio.js const CONSULTAS_CUOTAS = require('@altertex/util/const/consultasCuotas'); const correrQuery = require('@altertex/util/ser/correrQuery'); +/** + * Actualiza un set de cuotas en la base de datos. + * + * @async + * @param {number} idCuotaSet - ID del set de cuotas a actualizar. + * @param {object} cambios - Objeto con los cambios a aplicar. + * @param {string} cambios.nombre - Nombre actualizado del set de cuotas. + * @param {string} cambios.descripcion - Descripción del set de cuotas. + * @param {number} cambios.periodoRenovacion - Periodo de renovación en meses. + * @param {boolean} cambios.renovacionHabilitada - Si la renovación está habilitada. + * @param {Array} [cambios.productos=[]] - Lista de productos con límites. + * @param {number} cambios.productos[].idProducto - ID del producto asociado. + * @param {number} cambios.productos[].limite - Límite asignado al producto. + * @param {number} cambios.productos[].limiteActual - Límite actual del producto. + * @throws {Error} Si ocurre un error en la consulta a la base de datos. + */ exports.actualizarSetCuotas = async (idCuotaSet, cambios) => { const { nombre, descripcion, periodoRenovacion, renovacionHabilitada, productos = [] } = cambios; - // 1. Actualizar datos básicos de la cuota await correrQuery(CONSULTAS_CUOTAS.ACTUALIZAR_CUOTA_SET, [ nombre, descripcion, @@ -15,10 +29,8 @@ exports.actualizarSetCuotas = async (idCuotaSet, cambios) => { idCuotaSet ]); - // 2. Eliminar productos anteriores await correrQuery(CONSULTAS_CUOTAS.ELIMINAR_PRODUCTOS_CUOTA_SET, [idCuotaSet]); - // 3. Insertar nuevos productos (solo los que tienen ID válido) for (const producto of productos) { if (producto.idProducto && producto.idProducto > 0) { await correrQuery(CONSULTAS_CUOTAS.INSERTAR_CUOTA_PRODUCTO_ACTUALIZAR, [ @@ -29,4 +41,4 @@ exports.actualizarSetCuotas = async (idCuotaSet, cambios) => { ]); } } -}; \ No newline at end of file +}; diff --git a/Cuotas/Rutas/RutasIndividuales/actualizarSetCuotas.routes.js b/Cuotas/Rutas/RutasIndividuales/actualizarSetCuotas.routes.js index ead29c49..65860635 100644 --- a/Cuotas/Rutas/RutasIndividuales/actualizarSetCuotas.routes.js +++ b/Cuotas/Rutas/RutasIndividuales/actualizarSetCuotas.routes.js @@ -1,3 +1,21 @@ +/** + * Ruta para actualizar un set de cuotas. + * + * Método: PUT + * Ruta: /api/cuotas/actualizar-set-cuotas (o la que defina `RUTAS.CUOTAS.ACTUALIZAR_SET_CUOTAS`) + * + * Middleware aplicados: + * - revisión de API Key + * - autorización por token JWT + * - verificación de permisos de usuario + * + * Permiso requerido: PERMISOS.ACTUALIZAR_SET_CUOTAS + * + * @module actualizarSetCuotas.routes + */ + + + const express = require('express'); const controlador = require('@altertex/cuota/ctrl/actualizarSetCuotas.controller'); const autorizarToken = require('@altertex/util/inter/autorizarToken'); From ed5b689191bece682c4dc83dd4f29d83554a6bcb Mon Sep 17 00:00:00 2001 From: max Date: Fri, 6 Jun 2025 19:49:33 -0600 Subject: [PATCH 108/116] fix(actualizarUsuario): streamline password handling and improve error management --- .../actualizarUsuario.controller.js | 6 +- .../repositorioActualizarUsuario.js | 115 ++++++++++-------- 2 files changed, 65 insertions(+), 56 deletions(-) diff --git a/Usuarios/Controladores/actualizarUsuario.controller.js b/Usuarios/Controladores/actualizarUsuario.controller.js index f4159747..122a8ee1 100644 --- a/Usuarios/Controladores/actualizarUsuario.controller.js +++ b/Usuarios/Controladores/actualizarUsuario.controller.js @@ -28,8 +28,8 @@ exports.actualizarUsuario = async (req, res) => { } else if (req.body.cambios) { // Si la información viene en el formato esperado (hay cambios) datos = Array.isArray(req.body.cambios) ? req.body.cambios : [req.body.cambios]; - const contraseniaEncriptada = await bcrypt.hash(datos[0]['contrasenia'], 10); - datos[0]['contrasenia'] = contraseniaEncriptada; + const contraseniaEncriptada = await bcrypt.hash(datos[0].contrasenia, 10); + datos[0].contrasenia = contraseniaEncriptada; } else { return res .status(MENSAJES.ERROR_ACTUALIZAR_USUARIO.codigo) @@ -46,7 +46,7 @@ exports.actualizarUsuario = async (req, res) => { return res .status(MENSAJES.USUARIO_ACTUALIZADO.codigo) .json({ mensaje: MENSAJES.USUARIO_ACTUALIZADO.mensaje, datos }); - } catch (e) { + } catch { return res .status(MENSAJES.ERROR_ACTUALIZAR_USUARIO.codigo) .json({ mensaje: MENSAJES.ERROR_ACTUALIZAR_USUARIO.mensaje }); diff --git a/Usuarios/Datos/Repositorios/repositorioActualizarUsuario.js b/Usuarios/Datos/Repositorios/repositorioActualizarUsuario.js index cd216091..7bed3405 100644 --- a/Usuarios/Datos/Repositorios/repositorioActualizarUsuario.js +++ b/Usuarios/Datos/Repositorios/repositorioActualizarUsuario.js @@ -26,73 +26,82 @@ exports.actualizarUsuario = async (datos) => { } try { await Promise.all( - datos.map(async (usuario) => { - const { - idUsuario, - nombreCompleto, - correoElectronico, - contrasenia, - numeroTelefono, - direccion, - fechaNacimiento, - genero, - estatus, - idCliente, - } = usuario; - - const conContrasena = () => (usuario.contrasenia == '' ? false : true); - - // Actualiza datos del usuario - if (conContrasena()) { - await correrQuery(CONSULTAS_USUARIOS.ACTUALIZAR_DATOS_USUARIO, [ - nombreCompleto, - correoElectronico, - contrasenia, - numeroTelefono, - direccion, - fechaNacimiento, - genero, - estatus, + datos.map( + /** + * Actualiza la información de un usuario individualmente. + * @param {object} usuario - Objeto con los datos del usuario a actualizar. + * @returns {Promise} + */ + async (usuario) => { + const { idUsuario, - ]); - } else { - await correrQuery(CONSULTAS_USUARIOS.ACTUALIZAR_DATOS_USUARIO_SIN_CONTRASENA, [ nombreCompleto, correoElectronico, + contrasenia, numeroTelefono, direccion, fechaNacimiento, genero, estatus, - idUsuario, - ]); - } + } = usuario; + + const conContrasena = () => (usuario.contrasenia == '' ? false : true); + + // Actualiza datos del usuario + if (conContrasena()) { + await correrQuery(CONSULTAS_USUARIOS.ACTUALIZAR_DATOS_USUARIO, [ + nombreCompleto, + correoElectronico, + contrasenia, + numeroTelefono, + direccion, + fechaNacimiento, + genero, + estatus, + idUsuario, + ]); + } else { + await correrQuery(CONSULTAS_USUARIOS.ACTUALIZAR_DATOS_USUARIO_SIN_CONTRASENA, [ + nombreCompleto, + correoElectronico, + numeroTelefono, + direccion, + fechaNacimiento, + genero, + estatus, + idUsuario, + ]); + } - if (usuario.cliente) { - // Pasar el cliente/clientes a un array - const clientes = Array.isArray(usuario.cliente) ? usuario.cliente : [usuario.cliente]; + if (usuario.cliente) { + // Pasar el cliente/clientes a un array + const clientes = Array.isArray(usuario.cliente) ? usuario.cliente : [usuario.cliente]; - await correrQuery(CONSULTAS_USUARIOS.ELIMINAR_USUARIO_CLIENTE, [idUsuario]).then( - // Eliminar todos los cliente asociados al usuario - async () => { - try { - for (const cliente of clientes) { - // Asociar cada cliente seleccionado al usuario - if (cliente) { - // Evitar errores si el cliente es undefined o null - await correrQuery(CONSULTAS_USUARIOS.ASOCIAR_USUARIO_A_CLIENTE, [ - idUsuario, - cliente, - ]); + await correrQuery(CONSULTAS_USUARIOS.ELIMINAR_USUARIO_CLIENTE, [idUsuario]).then( + /** + * Función que reasocia los clientes al usuario después de eliminarlos. + * @returns {Promise} + */ + async () => { + try { + for (const cliente of clientes) { + // Asociar cada cliente seleccionado al usuario + if (cliente) { + // Evitar errores si el cliente es undefined o null + await correrQuery(CONSULTAS_USUARIOS.ASOCIAR_USUARIO_A_CLIENTE, [ + idUsuario, + cliente, + ]); + } } + } catch (error) { + return error; } - } catch (error) { - return error; } - } - ); + ); + } } - }) + ) ); } catch { throw new Error(MENSAJES.ERROR_ACTUALIZAR_USUARIO.mensaje); From 0a7d94380038013ee01c7c627601d56e6af30f7a Mon Sep 17 00:00:00 2001 From: max Date: Fri, 6 Jun 2025 20:04:35 -0600 Subject: [PATCH 109/116] fix(actualizarUsuario): simplify password check logic in user update function --- Usuarios/Datos/Repositorios/repositorioActualizarUsuario.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Usuarios/Datos/Repositorios/repositorioActualizarUsuario.js b/Usuarios/Datos/Repositorios/repositorioActualizarUsuario.js index 7bed3405..1bc17637 100644 --- a/Usuarios/Datos/Repositorios/repositorioActualizarUsuario.js +++ b/Usuarios/Datos/Repositorios/repositorioActualizarUsuario.js @@ -45,10 +45,10 @@ exports.actualizarUsuario = async (datos) => { estatus, } = usuario; - const conContrasena = () => (usuario.contrasenia == '' ? false : true); + const conContrasena = usuario.contrasenia == '' ? false : true; // Actualiza datos del usuario - if (conContrasena()) { + if (conContrasena) { await correrQuery(CONSULTAS_USUARIOS.ACTUALIZAR_DATOS_USUARIO, [ nombreCompleto, correoElectronico, From a96a863d4acd905736c1f7933552e741637048c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valeria=20Zu=C3=B1iga=20Mendoza?= Date: Fri, 6 Jun 2025 21:22:12 -0600 Subject: [PATCH 110/116] =?UTF-8?q?tests:=20Agregar=20pruebas=20autom?= =?UTF-8?q?=C3=A1ticas=20de=20consultar=20lista=20y=20actualizar=20sets=20?= =?UTF-8?q?ptoductos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Constantes/mensajesSetsProductos.js | 4 + ...actualizarSetsProductos.controller.test.js | 83 +++++++++++++++++++ ...ultarListaSetsProductos.controller.test.js | 81 ++++++++++++++++++ jest.config.js | 4 + 4 files changed, 172 insertions(+) create mode 100644 _tests_/SetsProductos/actualizarSetsProductos.controller.test.js create mode 100644 _tests_/SetsProductos/consultarListaSetsProductos.controller.test.js diff --git a/Utilidades/Constantes/mensajesSetsProductos.js b/Utilidades/Constantes/mensajesSetsProductos.js index a2333e04..3d8291c9 100644 --- a/Utilidades/Constantes/mensajesSetsProductos.js +++ b/Utilidades/Constantes/mensajesSetsProductos.js @@ -88,4 +88,8 @@ module.exports = { codigo: 400, mensaje: 'La lista de productos contiene elementos inválidos', }, + PARAMETROS_INVALIDOS: { + codigo: 400, + mensaje: 'Parámetros inválidos', + }, }; diff --git a/_tests_/SetsProductos/actualizarSetsProductos.controller.test.js b/_tests_/SetsProductos/actualizarSetsProductos.controller.test.js new file mode 100644 index 00000000..3aea3b8d --- /dev/null +++ b/_tests_/SetsProductos/actualizarSetsProductos.controller.test.js @@ -0,0 +1,83 @@ +const request = require('supertest'); +const express = require('express'); +const bodyParser = require('body-parser'); +const MENSAJES = require('@altertex/util/const/mensajesSetsProductos'); + +jest.mock('@altertex/setspro/repos/repositorioActualizarSetsProductos', () => ({ + actualizarSetProductos: jest.fn() +})); + +const repositorio = require('@altertex/setspro/repos/repositorioActualizarSetsProductos'); +const controller = require('@altertex/setspro/ctrl/actualizarSetsProductos.controller'); + +const app = express(); +app.use(bodyParser.json()); + +// 👇 Middleware para simular usuario con cliente seleccionado +app.use((req, res, next) => { + req.user = { clienteSeleccionado: 1 }; + next(); +}); + +app.put('/sets-productos', controller.actualizarSetProductos); + +describe('Controlador actualizarSetProductos', () => { + // Silenciar console.error durante las pruebas para evitar ruido en la salida + beforeAll(() => { + jest.spyOn(console, 'error').mockImplementation(() => {}); + }); + + // Restaurar el comportamiento original de console.error + afterAll(() => { + console.error.mockRestore(); + }); + + // Limpia los mocks después de cada test + afterEach(() => jest.clearAllMocks()); + + it('debe devolver 400 si faltan campos requeridos', async () => { + const res = await request(app).put('/sets-productos').send({ + nombre: 'Set incompleto' + }); + + expect(res.statusCode).toBe(MENSAJES.FORMATO_INVALIDO_DATOS.codigo); + expect(res.body.mensaje).toBe(MENSAJES.FORMATO_INVALIDO_DATOS.mensaje); + expect(res.body.detalles).toMatch(/nombre y lista de productos/i); + }); + + it('debe devolver 200 si la actualización es exitosa', async () => { + repositorio.actualizarSetProductos.mockResolvedValue(); + + const datos = { + idSetProducto: 1, + nombre: 'Set Actualizado', + descripcion: 'Nueva descripción', + activo: true, + productos: [101, 102] + }; + + const res = await request(app).put('/sets-productos').send(datos); + + expect(res.statusCode).toBe(MENSAJES.SET_ACTUALIZADO.codigo); + expect(res.body.mensaje).toBe(MENSAJES.SET_ACTUALIZADO.mensaje); + expect(res.body.datos).toEqual(datos); + expect(repositorio.actualizarSetProductos).toHaveBeenCalledWith(1, datos); + }); + + it('debe devolver 500 si el repositorio lanza un error', async () => { + repositorio.actualizarSetProductos.mockRejectedValue(new Error('Fallo DB')); + + const datos = { + idSetProducto: 2, + nombre: 'Set con error', + descripcion: 'desc', + activo: false, + productos: [] + }; + + const res = await request(app).put('/sets-productos').send(datos); + + expect(res.statusCode).toBe(MENSAJES.ERROR_ACTUALIZAR_SET.codigo); + expect(res.body.mensaje).toBe('Fallo DB'); + }); +}); diff --git a/_tests_/SetsProductos/consultarListaSetsProductos.controller.test.js b/_tests_/SetsProductos/consultarListaSetsProductos.controller.test.js new file mode 100644 index 00000000..3c4d609a --- /dev/null +++ b/_tests_/SetsProductos/consultarListaSetsProductos.controller.test.js @@ -0,0 +1,81 @@ +// Mocks antes de importar el controlador +jest.mock('@altertex/setspro/repos/repositorioConsultarSetsProductos', () => ({ + obtenerSetsProductos: jest.fn(), +})); + +// Importaciones +const controlador = require('@altertex/setspro/ctrl/consultarSetsProductos.controller'); +const repositorio = require('@altertex/setspro/repos/repositorioConsultarSetsProductos'); +const MENSAJES = require('@altertex/util/const/mensajesSetsProductos'); + +describe('Controlador consultarLista (sets de productos)', () => { + let req; + let res; + + beforeEach(() => { + jest.clearAllMocks(); + + req = { + user: { + clienteSeleccionado: '101', + }, + }; + + res = { + status: jest.fn().mockReturnThis(), + json: jest.fn(), + }; + + jest.spyOn(console, 'error').mockImplementation(() => {}); // Silenciar errores + }); + + // Escenario 1: ID de cliente inválido + test('Debe retornar error si el ID del cliente no es válido', async () => { + req.user.clienteSeleccionado = undefined; + + await controlador.consultarLista(req, res); + expect(res.status).toHaveBeenCalledWith(MENSAJES.PARAMETROS_INVALIDOS.codigo); + expect(res.json).toHaveBeenCalledWith({ + mensaje: MENSAJES.PARAMETROS_INVALIDOS.mensaje, + }); + }); + + // Escenario 2: Lista de sets obtenida exitosamente + test('Debe retornar la lista de sets si la consulta es exitosa', async () => { + const mockSets = [ + { + idSet: 1, + nombre: 'Set A', + productos: ['Producto 1', 'Producto 2'], + }, + { + idSet: 2, + nombre: 'Set B', + productos: ['Producto 3'], + }, + ]; + + repositorio.obtenerSetsProductos.mockResolvedValue(mockSets); + + await controlador.consultarLista(req, res); + + expect(repositorio.obtenerSetsProductos).toHaveBeenCalledWith(101); + expect(res.status).toHaveBeenCalledWith(MENSAJES.CONSULTA_EXITOSA.codigo); + expect(res.json).toHaveBeenCalledWith({ + mensaje: MENSAJES.CONSULTA_EXITOSA.mensaje, + setsProductos: mockSets, + }); + }); + + // Escenario 3: Error inesperado en el repositorio + test('Debe manejar errores inesperados del repositorio', async () => { + repositorio.obtenerSetsProductos.mockRejectedValue(new Error('Error')); + + await controlador.consultarLista(req, res); + + expect(res.status).toHaveBeenCalledWith(MENSAJES.ERROR_CONSULTAR_SETS_PRODUCTOS.codigo); + expect(res.json).toHaveBeenCalledWith({ + mensaje: MENSAJES.ERROR_CONSULTAR_SETS_PRODUCTOS.mensaje, + }); + }); +}); diff --git a/jest.config.js b/jest.config.js index f30998ff..9162afb7 100644 --- a/jest.config.js +++ b/jest.config.js @@ -92,6 +92,10 @@ module.exports = { '^@altertex/usu/datos/(.*)$': '/Usuarios/Datos/$1', '^@altertex/usu/(.*)$': '/Usuarios/$1', + // Sets de productos + "^@altertex/setspro/ctrl/(.*)$": "/SetsProductos/Controladores/$1", + "^@altertex/setspro/repos/(.*)$": "/SetsProductos/Datos/Repositorios/$1", + // Generic mapping as fallback '^@altertex/(.*)$': '/$1', From 45f6beedd0ad365ae5d6556596fcbacd1cfed0fe Mon Sep 17 00:00:00 2001 From: max Date: Fri, 6 Jun 2025 21:53:40 -0600 Subject: [PATCH 111/116] [FIX] - Arreglar actualizar rol de usuario --- .../Datos/Repositorios/repositorioActualizarUsuario.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Usuarios/Datos/Repositorios/repositorioActualizarUsuario.js b/Usuarios/Datos/Repositorios/repositorioActualizarUsuario.js index 1bc17637..cb7ed582 100644 --- a/Usuarios/Datos/Repositorios/repositorioActualizarUsuario.js +++ b/Usuarios/Datos/Repositorios/repositorioActualizarUsuario.js @@ -41,12 +41,15 @@ exports.actualizarUsuario = async (datos) => { numeroTelefono, direccion, fechaNacimiento, + idRol, genero, estatus, } = usuario; const conContrasena = usuario.contrasenia == '' ? false : true; + console.log('roles', idRol); + // Actualiza datos del usuario if (conContrasena) { await correrQuery(CONSULTAS_USUARIOS.ACTUALIZAR_DATOS_USUARIO, [ @@ -100,6 +103,12 @@ exports.actualizarUsuario = async (datos) => { } ); } + + const result = await correrQuery(CONSULTAS_USUARIOS.ACTUALIZAR_ROL_USUARIO, [ + idRol, + idUsuario, + ]); + console.log('Resultado de actualizar rol:', result); } ) ); From 12953d9d43c9af9754ae71aa24f167c945c340d9 Mon Sep 17 00:00:00 2001 From: max Date: Fri, 6 Jun 2025 21:59:26 -0600 Subject: [PATCH 112/116] =?UTF-8?q?integraci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Constantes/mensajesSetsProductos.js | 4 + ...actualizarSetsProductos.controller.test.js | 83 +++++++++++++++++++ ...ultarListaSetsProductos.controller.test.js | 81 ++++++++++++++++++ jest.config.js | 4 + 4 files changed, 172 insertions(+) create mode 100644 _tests_/SetsProductos/actualizarSetsProductos.controller.test.js create mode 100644 _tests_/SetsProductos/consultarListaSetsProductos.controller.test.js diff --git a/Utilidades/Constantes/mensajesSetsProductos.js b/Utilidades/Constantes/mensajesSetsProductos.js index a2333e04..3d8291c9 100644 --- a/Utilidades/Constantes/mensajesSetsProductos.js +++ b/Utilidades/Constantes/mensajesSetsProductos.js @@ -88,4 +88,8 @@ module.exports = { codigo: 400, mensaje: 'La lista de productos contiene elementos inválidos', }, + PARAMETROS_INVALIDOS: { + codigo: 400, + mensaje: 'Parámetros inválidos', + }, }; diff --git a/_tests_/SetsProductos/actualizarSetsProductos.controller.test.js b/_tests_/SetsProductos/actualizarSetsProductos.controller.test.js new file mode 100644 index 00000000..3aea3b8d --- /dev/null +++ b/_tests_/SetsProductos/actualizarSetsProductos.controller.test.js @@ -0,0 +1,83 @@ +const request = require('supertest'); +const express = require('express'); +const bodyParser = require('body-parser'); +const MENSAJES = require('@altertex/util/const/mensajesSetsProductos'); + +jest.mock('@altertex/setspro/repos/repositorioActualizarSetsProductos', () => ({ + actualizarSetProductos: jest.fn() +})); + +const repositorio = require('@altertex/setspro/repos/repositorioActualizarSetsProductos'); +const controller = require('@altertex/setspro/ctrl/actualizarSetsProductos.controller'); + +const app = express(); +app.use(bodyParser.json()); + +// 👇 Middleware para simular usuario con cliente seleccionado +app.use((req, res, next) => { + req.user = { clienteSeleccionado: 1 }; + next(); +}); + +app.put('/sets-productos', controller.actualizarSetProductos); + +describe('Controlador actualizarSetProductos', () => { + // Silenciar console.error durante las pruebas para evitar ruido en la salida + beforeAll(() => { + jest.spyOn(console, 'error').mockImplementation(() => {}); + }); + + // Restaurar el comportamiento original de console.error + afterAll(() => { + console.error.mockRestore(); + }); + + // Limpia los mocks después de cada test + afterEach(() => jest.clearAllMocks()); + + it('debe devolver 400 si faltan campos requeridos', async () => { + const res = await request(app).put('/sets-productos').send({ + nombre: 'Set incompleto' + }); + + expect(res.statusCode).toBe(MENSAJES.FORMATO_INVALIDO_DATOS.codigo); + expect(res.body.mensaje).toBe(MENSAJES.FORMATO_INVALIDO_DATOS.mensaje); + expect(res.body.detalles).toMatch(/nombre y lista de productos/i); + }); + + it('debe devolver 200 si la actualización es exitosa', async () => { + repositorio.actualizarSetProductos.mockResolvedValue(); + + const datos = { + idSetProducto: 1, + nombre: 'Set Actualizado', + descripcion: 'Nueva descripción', + activo: true, + productos: [101, 102] + }; + + const res = await request(app).put('/sets-productos').send(datos); + + expect(res.statusCode).toBe(MENSAJES.SET_ACTUALIZADO.codigo); + expect(res.body.mensaje).toBe(MENSAJES.SET_ACTUALIZADO.mensaje); + expect(res.body.datos).toEqual(datos); + expect(repositorio.actualizarSetProductos).toHaveBeenCalledWith(1, datos); + }); + + it('debe devolver 500 si el repositorio lanza un error', async () => { + repositorio.actualizarSetProductos.mockRejectedValue(new Error('Fallo DB')); + + const datos = { + idSetProducto: 2, + nombre: 'Set con error', + descripcion: 'desc', + activo: false, + productos: [] + }; + + const res = await request(app).put('/sets-productos').send(datos); + + expect(res.statusCode).toBe(MENSAJES.ERROR_ACTUALIZAR_SET.codigo); + expect(res.body.mensaje).toBe('Fallo DB'); + }); +}); diff --git a/_tests_/SetsProductos/consultarListaSetsProductos.controller.test.js b/_tests_/SetsProductos/consultarListaSetsProductos.controller.test.js new file mode 100644 index 00000000..3c4d609a --- /dev/null +++ b/_tests_/SetsProductos/consultarListaSetsProductos.controller.test.js @@ -0,0 +1,81 @@ +// Mocks antes de importar el controlador +jest.mock('@altertex/setspro/repos/repositorioConsultarSetsProductos', () => ({ + obtenerSetsProductos: jest.fn(), +})); + +// Importaciones +const controlador = require('@altertex/setspro/ctrl/consultarSetsProductos.controller'); +const repositorio = require('@altertex/setspro/repos/repositorioConsultarSetsProductos'); +const MENSAJES = require('@altertex/util/const/mensajesSetsProductos'); + +describe('Controlador consultarLista (sets de productos)', () => { + let req; + let res; + + beforeEach(() => { + jest.clearAllMocks(); + + req = { + user: { + clienteSeleccionado: '101', + }, + }; + + res = { + status: jest.fn().mockReturnThis(), + json: jest.fn(), + }; + + jest.spyOn(console, 'error').mockImplementation(() => {}); // Silenciar errores + }); + + // Escenario 1: ID de cliente inválido + test('Debe retornar error si el ID del cliente no es válido', async () => { + req.user.clienteSeleccionado = undefined; + + await controlador.consultarLista(req, res); + expect(res.status).toHaveBeenCalledWith(MENSAJES.PARAMETROS_INVALIDOS.codigo); + expect(res.json).toHaveBeenCalledWith({ + mensaje: MENSAJES.PARAMETROS_INVALIDOS.mensaje, + }); + }); + + // Escenario 2: Lista de sets obtenida exitosamente + test('Debe retornar la lista de sets si la consulta es exitosa', async () => { + const mockSets = [ + { + idSet: 1, + nombre: 'Set A', + productos: ['Producto 1', 'Producto 2'], + }, + { + idSet: 2, + nombre: 'Set B', + productos: ['Producto 3'], + }, + ]; + + repositorio.obtenerSetsProductos.mockResolvedValue(mockSets); + + await controlador.consultarLista(req, res); + + expect(repositorio.obtenerSetsProductos).toHaveBeenCalledWith(101); + expect(res.status).toHaveBeenCalledWith(MENSAJES.CONSULTA_EXITOSA.codigo); + expect(res.json).toHaveBeenCalledWith({ + mensaje: MENSAJES.CONSULTA_EXITOSA.mensaje, + setsProductos: mockSets, + }); + }); + + // Escenario 3: Error inesperado en el repositorio + test('Debe manejar errores inesperados del repositorio', async () => { + repositorio.obtenerSetsProductos.mockRejectedValue(new Error('Error')); + + await controlador.consultarLista(req, res); + + expect(res.status).toHaveBeenCalledWith(MENSAJES.ERROR_CONSULTAR_SETS_PRODUCTOS.codigo); + expect(res.json).toHaveBeenCalledWith({ + mensaje: MENSAJES.ERROR_CONSULTAR_SETS_PRODUCTOS.mensaje, + }); + }); +}); diff --git a/jest.config.js b/jest.config.js index f30998ff..9162afb7 100644 --- a/jest.config.js +++ b/jest.config.js @@ -92,6 +92,10 @@ module.exports = { '^@altertex/usu/datos/(.*)$': '/Usuarios/Datos/$1', '^@altertex/usu/(.*)$': '/Usuarios/$1', + // Sets de productos + "^@altertex/setspro/ctrl/(.*)$": "/SetsProductos/Controladores/$1", + "^@altertex/setspro/repos/(.*)$": "/SetsProductos/Datos/Repositorios/$1", + // Generic mapping as fallback '^@altertex/(.*)$': '/$1', From 8f574ebe629538a307d4f11e136551a26aa35610 Mon Sep 17 00:00:00 2001 From: NicoH00d Date: Fri, 6 Jun 2025 23:04:09 -0600 Subject: [PATCH 113/116] fix: arreglar consulta --- Utilidades/Constantes/consultasUsuarios.js | 1 + 1 file changed, 1 insertion(+) diff --git a/Utilidades/Constantes/consultasUsuarios.js b/Utilidades/Constantes/consultasUsuarios.js index bbf590d5..e0a5d56f 100644 --- a/Utilidades/Constantes/consultasUsuarios.js +++ b/Utilidades/Constantes/consultasUsuarios.js @@ -60,6 +60,7 @@ module.exports = { u.genero, u.estatus, r.idRol AS rol, + r.nombre AS nombreRol, uc.idCliente AS idCliente, c.nombreComercial AS nombreCliente FROM usuario u From 565d7d65c034d055eea7aef085bca7991986e160 Mon Sep 17 00:00:00 2001 From: ArturoSanRod Date: Sat, 7 Jun 2025 18:39:26 -0600 Subject: [PATCH 114/116] Implementacion de prueba --- .../actualizarSetCuotas.controller.test.js | 242 ++++++++++++++++++ 1 file changed, 242 insertions(+) create mode 100644 _tests_/Cuotas/Controladores/actualizarSetCuotas.controller.test.js diff --git a/_tests_/Cuotas/Controladores/actualizarSetCuotas.controller.test.js b/_tests_/Cuotas/Controladores/actualizarSetCuotas.controller.test.js new file mode 100644 index 00000000..ceb45d0d --- /dev/null +++ b/_tests_/Cuotas/Controladores/actualizarSetCuotas.controller.test.js @@ -0,0 +1,242 @@ +/** + * Mocks antes de importar el controlador + */ +jest.mock('@altertex/cuota/repos/actualizarSetCuotasRepositorio', () => ({ + actualizarSetCuotas: jest.fn(), +})); + +const controladorActualizarSetCuotas = require('@altertex/cuota/ctrl/actualizarSetCuotas.controller'); +const repositorio = require('@altertex/cuota/repos/actualizarSetCuotasRepositorio'); + +describe('Controlador de Actualizar Set de Cuotas', () => { + let req; + let res; + + const dataMockCompleto = { + idCuotaSet: 123, + cambios: { + nombre: 'Plan Premium Actualizado', + descripcion: 'Plan premium con nuevas características', + periodoRenovacion: 12, + renovacionHabilitada: true, + productos: [ + { idProducto: 1, limite: 200, limiteActual: 150 }, + { idProducto: 2, limite: 100, limiteActual: 75 }, + { idProducto: 3, limite: 50, limiteActual: 25 } + ] + } + }; + + const dataMockSinProductos = { + idCuotaSet: 456, + cambios: { + nombre: 'Plan Básico Actualizado', + descripcion: 'Plan básico sin productos', + periodoRenovacion: 1, + renovacionHabilitada: false, + productos: [] + } + }; + + beforeEach(() => { + jest.clearAllMocks(); + + req = { + body: {}, + }; + res = { + status: jest.fn().mockReturnThis(), + json: jest.fn(), + }; + }); + + test('Debe actualizar un set de cuotas exitosamente con productos', async () => { + req.body = dataMockCompleto; + repositorio.actualizarSetCuotas.mockResolvedValue(); + + await controladorActualizarSetCuotas.actualizarSetCuotas(req, res); + + expect(repositorio.actualizarSetCuotas).toHaveBeenCalledWith( + 123, + dataMockCompleto.cambios + ); + + expect(res.status).toHaveBeenCalledWith(200); + expect(res.json).toHaveBeenCalledWith({ + mensaje: 'Set de cuotas actualizado correctamente' + }); + }); + + test('Debe actualizar un set de cuotas exitosamente sin productos', async () => { + req.body = dataMockSinProductos; + repositorio.actualizarSetCuotas.mockResolvedValue(); + + await controladorActualizarSetCuotas.actualizarSetCuotas(req, res); + + expect(repositorio.actualizarSetCuotas).toHaveBeenCalledWith( + 456, + dataMockSinProductos.cambios + ); + + expect(res.status).toHaveBeenCalledWith(200); + expect(res.json).toHaveBeenCalledWith({ + mensaje: 'Set de cuotas actualizado correctamente' + }); + }); + + test('Debe retornar error 400 cuando falta idCuotaSet', async () => { + req.body = { + cambios: dataMockCompleto.cambios + }; + + await controladorActualizarSetCuotas.actualizarSetCuotas(req, res); + + expect(repositorio.actualizarSetCuotas).not.toHaveBeenCalled(); + expect(res.status).toHaveBeenCalledWith(400); + expect(res.json).toHaveBeenCalledWith({ + mensaje: 'Datos incompletos' + }); + }); + + test('Debe retornar error 400 cuando faltan cambios', async () => { + req.body = { + idCuotaSet: 123 + }; + + await controladorActualizarSetCuotas.actualizarSetCuotas(req, res); + + expect(repositorio.actualizarSetCuotas).not.toHaveBeenCalled(); + expect(res.status).toHaveBeenCalledWith(400); + expect(res.json).toHaveBeenCalledWith({ + mensaje: 'Datos incompletos' + }); + }); + + test('Debe retornar error 400 cuando idCuotaSet es null', async () => { + req.body = { + idCuotaSet: null, + cambios: dataMockCompleto.cambios + }; + + await controladorActualizarSetCuotas.actualizarSetCuotas(req, res); + + expect(repositorio.actualizarSetCuotas).not.toHaveBeenCalled(); + expect(res.status).toHaveBeenCalledWith(400); + expect(res.json).toHaveBeenCalledWith({ + mensaje: 'Datos incompletos' + }); + }); + + test('Debe retornar error 400 cuando cambios es null', async () => { + req.body = { + idCuotaSet: 123, + cambios: null + }; + + await controladorActualizarSetCuotas.actualizarSetCuotas(req, res); + + expect(repositorio.actualizarSetCuotas).not.toHaveBeenCalled(); + expect(res.status).toHaveBeenCalledWith(400); + expect(res.json).toHaveBeenCalledWith({ + mensaje: 'Datos incompletos' + }); + }); + + test('Debe retornar error 400 cuando el body está vacío', async () => { + req.body = {}; + + await controladorActualizarSetCuotas.actualizarSetCuotas(req, res); + + expect(repositorio.actualizarSetCuotas).not.toHaveBeenCalled(); + expect(res.status).toHaveBeenCalledWith(400); + expect(res.json).toHaveBeenCalledWith({ + mensaje: 'Datos incompletos' + }); + }); + + test('Debe manejar errores del repositorio y retornar error 500', async () => { + req.body = dataMockCompleto; + const errorMensaje = 'Error de conexión a la base de datos'; + repositorio.actualizarSetCuotas.mockRejectedValue(new Error(errorMensaje)); + + await controladorActualizarSetCuotas.actualizarSetCuotas(req, res); + + expect(repositorio.actualizarSetCuotas).toHaveBeenCalledWith( + 123, + dataMockCompleto.cambios + ); + + expect(res.status).toHaveBeenCalledWith(500); + expect(res.json).toHaveBeenCalledWith({ + mensaje: errorMensaje + }); + }); + + test('Debe manejar errores de validación SQL y retornar error 500', async () => { + req.body = dataMockCompleto; + const errorSQL = 'Constraint violation: Foreign key constraint fails'; + repositorio.actualizarSetCuotas.mockRejectedValue(new Error(errorSQL)); + + await controladorActualizarSetCuotas.actualizarSetCuotas(req, res); + + expect(repositorio.actualizarSetCuotas).toHaveBeenCalledWith( + 123, + dataMockCompleto.cambios + ); + + expect(res.status).toHaveBeenCalledWith(500); + expect(res.json).toHaveBeenCalledWith({ + mensaje: errorSQL + }); + }); + + test('Debe actualizar correctamente con idCuotaSet como string numérico', async () => { + req.body = { + idCuotaSet: '789', + cambios: dataMockCompleto.cambios + }; + repositorio.actualizarSetCuotas.mockResolvedValue(); + + await controladorActualizarSetCuotas.actualizarSetCuotas(req, res); + + expect(repositorio.actualizarSetCuotas).toHaveBeenCalledWith( + '789', + dataMockCompleto.cambios + ); + + expect(res.status).toHaveBeenCalledWith(200); + expect(res.json).toHaveBeenCalledWith({ + mensaje: 'Set de cuotas actualizado correctamente' + }); + }); + + test('Debe manejar productos con idProducto en 0 o negativos', async () => { + req.body = { + idCuotaSet: 123, + cambios: { + nombre: 'Test', + descripcion: 'Test desc', + periodoRenovacion: 1, + renovacionHabilitada: true, + productos: [ + { idProducto: 0, limite: 100, limiteActual: 50 }, + { idProducto: -1, limite: 200, limiteActual: 100 }, + { idProducto: 1, limite: 300, limiteActual: 150 } + ] + } + }; + repositorio.actualizarSetCuotas.mockResolvedValue(); + + await controladorActualizarSetCuotas.actualizarSetCuotas(req, res); + + expect(repositorio.actualizarSetCuotas).toHaveBeenCalledWith( + 123, + req.body.cambios + ); + + expect(res.status).toHaveBeenCalledWith(200); + expect(res.json).toHaveBeenCalledWith({ + mensaje: 'Set de cuotas actualizado correctamente' + }); + }); +}); \ No newline at end of file From 277fbfa782e19f3b54c2a88b757d3dc77aa43bba Mon Sep 17 00:00:00 2001 From: NicoH00d Date: Thu, 12 Jun 2025 23:23:37 -0600 Subject: [PATCH 115/116] Manejo de errores con nuevo estandar --- .../actualizarUsuario.controller.js | 41 ++--- .../repositorioActualizarUsuario.js | 152 ++++++++---------- Utilidades/Constantes/consultasUsuarios.js | 5 + Utilidades/Constantes/mensajesUsuarios.js | 4 + 4 files changed, 94 insertions(+), 108 deletions(-) diff --git a/Usuarios/Controladores/actualizarUsuario.controller.js b/Usuarios/Controladores/actualizarUsuario.controller.js index 122a8ee1..e6538745 100644 --- a/Usuarios/Controladores/actualizarUsuario.controller.js +++ b/Usuarios/Controladores/actualizarUsuario.controller.js @@ -1,4 +1,3 @@ -const MENSAJES = require('@altertex/util/const/mensajesUsuarios'); const repositorio = require('@altertex/usu/repos/repositorioActualizarUsuario'); const bcrypt = require('bcryptjs'); @@ -20,35 +19,27 @@ const bcrypt = require('bcryptjs'); * @returns {Promise} Retorna una respuesta JSON indicando éxito o un error. */ exports.actualizarUsuario = async (req, res) => { - let datos; + const cambios = req.body.cambios || req.body; - // Si no hay cambios - if (req.body.id || req.body.idUsuario) { - datos = [req.body]; - } else if (req.body.cambios) { - // Si la información viene en el formato esperado (hay cambios) - datos = Array.isArray(req.body.cambios) ? req.body.cambios : [req.body.cambios]; - const contraseniaEncriptada = await bcrypt.hash(datos[0].contrasenia, 10); - datos[0].contrasenia = contraseniaEncriptada; - } else { - return res - .status(MENSAJES.ERROR_ACTUALIZAR_USUARIO.codigo) - .json({ mensaje: MENSAJES.ERROR_ACTUALIZAR_USUARIO.mensaje }); + if (!cambios) { + return res.status(400).json({ mensaje: 'No se enviaron los datos del usuario' }); + } + + const datos = Array.isArray(cambios) ? cambios : [cambios]; + + if (!datos[0].idUsuario) { + return res.status(400).json({ mensaje: 'ID del usuario no proporcionado' }); } - if (!datos || datos.length === 0) { - return res - .status(MENSAJES.ERROR_ACTUALIZAR_USUARIO.codigo) - .json({ mensaje: MENSAJES.ERROR_ACTUALIZAR_USUARIO.mensaje }); + if (datos[0].contrasenia) { + const contraseniaEncriptada = await bcrypt.hash(datos[0].contrasenia, 10); + datos[0].contrasenia = contraseniaEncriptada; } + try { await repositorio.actualizarUsuario(datos); - return res - .status(MENSAJES.USUARIO_ACTUALIZADO.codigo) - .json({ mensaje: MENSAJES.USUARIO_ACTUALIZADO.mensaje, datos }); - } catch { - return res - .status(MENSAJES.ERROR_ACTUALIZAR_USUARIO.codigo) - .json({ mensaje: MENSAJES.ERROR_ACTUALIZAR_USUARIO.mensaje }); + return res.status(200).json({ mensaje: 'Usuario actualizado correctamente' }); + } catch (error) { + return res.status(400).json({ mensaje: error.message }); } }; diff --git a/Usuarios/Datos/Repositorios/repositorioActualizarUsuario.js b/Usuarios/Datos/Repositorios/repositorioActualizarUsuario.js index cb7ed582..8100363a 100644 --- a/Usuarios/Datos/Repositorios/repositorioActualizarUsuario.js +++ b/Usuarios/Datos/Repositorios/repositorioActualizarUsuario.js @@ -22,97 +22,83 @@ const CONSULTAS_USUARIOS = require('@altertex/util/const/consultasUsuarios'); */ exports.actualizarUsuario = async (datos) => { if (!Array.isArray(datos) || datos.length === 0) { - throw new Error('Sin datos para actualizar.'); + throw new Error(MENSAJES.ERROR_OBTENER_USUARIO.mensaje); } + try { - await Promise.all( - datos.map( - /** - * Actualiza la información de un usuario individualmente. - * @param {object} usuario - Objeto con los datos del usuario a actualizar. - * @returns {Promise} - */ - async (usuario) => { - const { - idUsuario, - nombreCompleto, - correoElectronico, - contrasenia, - numeroTelefono, - direccion, - fechaNacimiento, - idRol, - genero, - estatus, - } = usuario; + for (const usuario of datos) { + const { + idUsuario, + correoElectronico, + nombreCompleto, + contrasenia, + numeroTelefono, + direccion, + fechaNacimiento, + idRol, + genero, + estatus, + cliente, + } = usuario; - const conContrasena = usuario.contrasenia == '' ? false : true; + const resultadoCorreo = await correrQuery( + CONSULTAS_USUARIOS.VALIDAR_CORREO_DUPLICADO_ACTUALIZACION, + [correoElectronico, idUsuario] + ); - console.log('roles', idRol); + if (resultadoCorreo.length > 0) { + throw new Error(MENSAJES.USUARIO_YA_EXISTE.mensaje); + } - // Actualiza datos del usuario - if (conContrasena) { - await correrQuery(CONSULTAS_USUARIOS.ACTUALIZAR_DATOS_USUARIO, [ - nombreCompleto, - correoElectronico, - contrasenia, - numeroTelefono, - direccion, - fechaNacimiento, - genero, - estatus, - idUsuario, - ]); - } else { - await correrQuery(CONSULTAS_USUARIOS.ACTUALIZAR_DATOS_USUARIO_SIN_CONTRASENA, [ - nombreCompleto, - correoElectronico, - numeroTelefono, - direccion, - fechaNacimiento, - genero, - estatus, - idUsuario, - ]); - } + const conContrasena = contrasenia && contrasenia.trim() !== ''; - if (usuario.cliente) { - // Pasar el cliente/clientes a un array - const clientes = Array.isArray(usuario.cliente) ? usuario.cliente : [usuario.cliente]; + if (conContrasena) { + await correrQuery(CONSULTAS_USUARIOS.ACTUALIZAR_DATOS_USUARIO, [ + nombreCompleto, + correoElectronico, + contrasenia, + numeroTelefono, + direccion, + fechaNacimiento, + genero, + estatus, + idUsuario, + ]); + } else { + await correrQuery(CONSULTAS_USUARIOS.ACTUALIZAR_DATOS_USUARIO_SIN_CONTRASENA, [ + nombreCompleto, + correoElectronico, + numeroTelefono, + direccion, + fechaNacimiento, + genero, + estatus, + idUsuario, + ]); + } - await correrQuery(CONSULTAS_USUARIOS.ELIMINAR_USUARIO_CLIENTE, [idUsuario]).then( - /** - * Función que reasocia los clientes al usuario después de eliminarlos. - * @returns {Promise} - */ - async () => { - try { - for (const cliente of clientes) { - // Asociar cada cliente seleccionado al usuario - if (cliente) { - // Evitar errores si el cliente es undefined o null - await correrQuery(CONSULTAS_USUARIOS.ASOCIAR_USUARIO_A_CLIENTE, [ - idUsuario, - cliente, - ]); - } - } - } catch (error) { - return error; - } - } - ); - } + // Asociar cliente(s) + if (cliente) { + const clientes = Array.isArray(cliente) ? cliente : [cliente]; + + // Eliminar asociaciones anteriores + await correrQuery(CONSULTAS_USUARIOS.ELIMINAR_USUARIO_CLIENTE, [idUsuario]); - const result = await correrQuery(CONSULTAS_USUARIOS.ACTUALIZAR_ROL_USUARIO, [ - idRol, - idUsuario, - ]); - console.log('Resultado de actualizar rol:', result); + for (const idCliente of clientes) { + if (idCliente) { + await correrQuery(CONSULTAS_USUARIOS.ASOCIAR_USUARIO_A_CLIENTE, [ + idUsuario, + idCliente, + ]); + } } - ) - ); - } catch { - throw new Error(MENSAJES.ERROR_ACTUALIZAR_USUARIO.mensaje); + } + await correrQuery(CONSULTAS_USUARIOS.ACTUALIZAR_ROL_USUARIO, [idRol, idUsuario]); + } + } catch (error) { + if (error.code === 'ER_TRUNCATED_WRONG_VALUE') { + throw new Error(MENSAJES.ERROR_FECHA_NO_VALIDA.mensaje); + } + throw new Error(error.message || MENSAJES.ERROR_ACTUALIZAR_USUARIO.mensaje); } }; diff --git a/Utilidades/Constantes/consultasUsuarios.js b/Utilidades/Constantes/consultasUsuarios.js index e0a5d56f..e2062013 100644 --- a/Utilidades/Constantes/consultasUsuarios.js +++ b/Utilidades/Constantes/consultasUsuarios.js @@ -220,4 +220,9 @@ module.exports = { WHERE idUsuario IN (?) AND puedeActivar2FA = true; `, + VALIDAR_CORREO_DUPLICADO_ACTUALIZACION: ` + SELECT idUsuario + FROM usuario + WHERE correoElectronico = ? AND idUsuario <> ?; + `, }; diff --git a/Utilidades/Constantes/mensajesUsuarios.js b/Utilidades/Constantes/mensajesUsuarios.js index b6675267..9e01f97d 100644 --- a/Utilidades/Constantes/mensajesUsuarios.js +++ b/Utilidades/Constantes/mensajesUsuarios.js @@ -95,4 +95,8 @@ module.exports = { codigo: 500, mensaje: 'Ocurrió un error al intentar actualizar el usuario.', }, + ERROR_FECHA_NO_VALIDA: { + codigo: 500, + mensaje: 'La fecha de nacimiento proporcionada no es válida.', + }, }; From fd6df3fc58bb54762b55a56d81b7ea91f0c52883 Mon Sep 17 00:00:00 2001 From: NicoH00d Date: Thu, 12 Jun 2025 23:33:30 -0600 Subject: [PATCH 116/116] =?UTF-8?q?Validaci=C3=B3n=20para=20eliminar=20al?= =?UTF-8?q?=20usuario=20logeado?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Usuarios/Controladores/eliminarUsuario.controller.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Usuarios/Controladores/eliminarUsuario.controller.js b/Usuarios/Controladores/eliminarUsuario.controller.js index 7078f34a..7bf238c5 100644 --- a/Usuarios/Controladores/eliminarUsuario.controller.js +++ b/Usuarios/Controladores/eliminarUsuario.controller.js @@ -42,6 +42,12 @@ exports.eliminarUsuario = async (req, res) => { } const idsNumericos = idsUsuarios.map(Number); + + if (idsNumericos.includes(idSolicitante)) { + return res.status(403).json({ + mensaje: 'No puedes eliminar tu propio usuario.', + }); + } const [usuariosObjetivo] = await db.query(` SELECT u.idUsuario, r.nombre AS rol