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 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/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/Categorias/Controladores/actualizarCategoria.controller.js b/Categorias/Controladores/actualizarCategoria.controller.js new file mode 100644 index 00000000..8138e24d --- /dev/null +++ b/Categorias/Controladores/actualizarCategoria.controller.js @@ -0,0 +1,44 @@ +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 {express.Request} req + * @param {express.Response} res + * @returns {Promise} + */ +exports.actualizarCategoria = async (req, res) => { + try { + const { idCategoria } = req.params; + const { nombreCategoria, descripcion, productos } = req.body; + + 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 { + return res.status(500).json(MENSAJES.ERROR_CREAR_CATEGORIA); + } +}; \ No newline at end of file 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/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/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/actualizarCategorias.routes.js b/Categorias/Rutas/RutasIndividuales/actualizarCategorias.routes.js new file mode 100644 index 00000000..67956f7d --- /dev/null +++ b/Categorias/Rutas/RutasIndividuales/actualizarCategorias.routes.js @@ -0,0 +1,72 @@ +/** + * 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'); +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), + controlador.actualizarCategoria +); + +module.exports = ruteador; \ 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/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/Categorias/Rutas/indexCategorias.routes.js b/Categorias/Rutas/indexCategorias.routes.js index 5555b343..d412692f 100644 --- a/Categorias/Rutas/indexCategorias.routes.js +++ b/Categorias/Rutas/indexCategorias.routes.js @@ -3,11 +3,15 @@ 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 rutasActualizarCategoria = require('@altertex/cat/rutasInd/actualizarCategorias.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); +ruteador.use(RUTAS.CATEGORIAS.BASE, rutasActualizarCategoria); module.exports = ruteador; 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/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/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/Cuotas/Controladores/actualizarSetCuotas.controller.js b/Cuotas/Controladores/actualizarSetCuotas.controller.js new file mode 100644 index 00000000..7c5ae1da --- /dev/null +++ b/Cuotas/Controladores/actualizarSetCuotas.controller.js @@ -0,0 +1,24 @@ +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; + + if (!idCuotaSet || !cambios) { + return res.status(400).json({ mensaje: 'Datos incompletos' }); + } + + await repositorio.actualizarSetCuotas(idCuotaSet, cambios); + return res.status(200).json({ mensaje: 'Set de cuotas actualizado correctamente' }); + + } catch (error) { + return res.status(500).json({ mensaje: error.message }); + } +}; diff --git a/Cuotas/Controladores/leerSetCuotas.controller.js b/Cuotas/Controladores/leerSetCuotas.controller.js new file mode 100644 index 00000000..9c33418d --- /dev/null +++ b/Cuotas/Controladores/leerSetCuotas.controller.js @@ -0,0 +1,44 @@ +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 `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 `idSetCuota` en el cuerpo. + * @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) + */ +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.obtenerSetCuotaPorId(idSetCuota); + + if (!setCuota) { + return res + .status(MENSAJES_CUOTAS.SET_CUOTA_NO_ENCONTRADO.codigo) + .json({ mensaje: MENSAJES_CUOTAS.SET_CUOTA_NO_ENCONTRADO.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_SET_CUOTA.codigo) + .json({ mensaje: MENSAJES_CUOTAS.ERROR_OBTENER_SET_CUOTA.mensaje }); + } +}; diff --git a/Cuotas/Datos/Repositorios/actualizarSetCuotasRepositorio.js b/Cuotas/Datos/Repositorios/actualizarSetCuotasRepositorio.js new file mode 100644 index 00000000..158b6a66 --- /dev/null +++ b/Cuotas/Datos/Repositorios/actualizarSetCuotasRepositorio.js @@ -0,0 +1,44 @@ +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; + + await correrQuery(CONSULTAS_CUOTAS.ACTUALIZAR_CUOTA_SET, [ + nombre, + descripcion, + periodoRenovacion, + renovacionHabilitada, + new Date(), + idCuotaSet + ]); + + await correrQuery(CONSULTAS_CUOTAS.ELIMINAR_PRODUCTOS_CUOTA_SET, [idCuotaSet]); + + for (const producto of productos) { + if (producto.idProducto && producto.idProducto > 0) { + await correrQuery(CONSULTAS_CUOTAS.INSERTAR_CUOTA_PRODUCTO_ACTUALIZAR, [ + idCuotaSet, + producto.idProducto, + producto.limite || 0, + producto.limiteActual || 0 + ]); + } + } +}; diff --git a/Cuotas/Datos/Repositorios/leerSetCuotasRepositorio.js b/Cuotas/Datos/Repositorios/leerSetCuotasRepositorio.js new file mode 100644 index 00000000..b939a7b6 --- /dev/null +++ b/Cuotas/Datos/Repositorios/leerSetCuotasRepositorio.js @@ -0,0 +1,55 @@ +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} 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. + */ +exports.obtenerSetCuotaPorId = async (idSetCuota) => { + + const query = CONSULTAS_CUOTAS.LEER_CUOTA_SET; + const queryCuotas = CONSULTAS_CUOTAS.LEER_CUOTA_SET_PRODUCTOS; + + const resultado = await correrQuery(query, [idSetCuota]); + + if (resultado.length === 0) return null; + + const productosCuota = await correrQuery(queryCuotas, [idSetCuota]); + + const productos = productosCuota.map((producto) => ({ + idProducto: producto.idProducto, + nombre: producto.nombreComun, + nombreComun: producto.nombreComun, + cuota_valor: producto.cuota_valor, + limite_actual: producto.limite_actual, + valor: producto.cuota_valor, + limite: producto.cuota_valor, + limiteActual: producto.limite_actual + })); + + const cuotas = productosCuota.map((producto) => ({ + valor: producto.cuota_valor, + })); + + const setCuota = { + idSetCuota: resultado[0].idCuotaSet, + idCuotaSet: resultado[0].idCuotaSet, + + nombre: resultado[0].nombre, + descripcion: resultado[0].descripcion, + + periodoRenovacion: resultado[0].periodoRenovacion, + renovacionHabilitada: resultado[0].renovacionHabilitada, + ultimaActualizacion: resultado[0].ultimaActualizacion, + productos, + cuotas, + }; + + + return setCuota; +}; \ No newline at end of file diff --git a/Cuotas/Rutas/RutasIndividuales/actualizarSetCuotas.routes.js b/Cuotas/Rutas/RutasIndividuales/actualizarSetCuotas.routes.js new file mode 100644 index 00000000..65860635 --- /dev/null +++ b/Cuotas/Rutas/RutasIndividuales/actualizarSetCuotas.routes.js @@ -0,0 +1,37 @@ +/** + * 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'); +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/RutasIndividuales/leerSetCuotas.routes.js b/Cuotas/Rutas/RutasIndividuales/leerSetCuotas.routes.js new file mode 100644 index 00000000..e5481465 --- /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..0c4056a8 100644 --- a/Cuotas/Rutas/indexCuotas.routes.js +++ b/Cuotas/Rutas/indexCuotas.routes.js @@ -1,17 +1,21 @@ -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 rutaActualizarSetCuotas = require('@altertex/cuota/rutasInd/actualizarSetCuotas.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); +ruteador.use(RUTAS.CUOTAS.BASE, rutaActualizarSetCuotas); module.exports = ruteador; diff --git a/Empleados/Controladores/actualizarGrupoEmpleado.controller.js b/Empleados/Controladores/actualizarGrupoEmpleado.controller.js new file mode 100644 index 00000000..3687ffcc --- /dev/null +++ b/Empleados/Controladores/actualizarGrupoEmpleado.controller.js @@ -0,0 +1,39 @@ +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) { + 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/Controladores/crearEmpleado.controller.js b/Empleados/Controladores/crearEmpleado.controller.js new file mode 100644 index 00000000..4ecb4a14 --- /dev/null +++ b/Empleados/Controladores/crearEmpleado.controller.js @@ -0,0 +1,172 @@ +const bcrypt = require('bcryptjs'); +const repositorio = require('@altertex/emp/repos/repositorioCrearEmpleado'); + +/** + * 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 {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[].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. + * @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 { + nombreCompleto, + correoElectronico, + contrasenia, + numeroTelefono, + direccion, + fechaNacimiento, + genero, + estatus, + idRol, + idCliente, + numeroEmergencia, + areaTrabajo, + posicion, + cantidadPuntos, + antiguedad, + } = req.body; + + // Validaciones críticas + if (!idCliente) { + 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 + ) { + 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(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 + || !tieneCaracterEspecial.test(contrasenia) + || !tieneMayuscula.test(contrasenia) + ) { + return res + .status(400) + .json({ + mensaje: + 'La contraseña es débil. Debe tener al menos 8 caracteres, una mayúscula y un caracter especial.', + }); + } + 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 (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(400) + .json({ mensaje: 'Los puntos deben ser un número entero mayor o igual a 0' }); + } + const telefonoValido = /^\d{10}$/; + 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(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({ + nombreCompleto, + correoElectronico, + contrasenia: contraseniaEncriptada, + numeroTelefono, + direccion, + fechaNacimiento, + genero, + estatus, + idRol, + idCliente, + numeroEmergencia, + areaTrabajo, + posicion, + cantidadPuntos, + antiguedad, + }); + return res.status(201).json({ + mensaje: 'Empleado creado exitosamente', + datos: resultado, + }); + } catch (error) { + return res.status(400).json({ mensaje: error.message }); + } +}; diff --git a/Empleados/Controladores/exportarEmpleados.controller.js b/Empleados/Controladores/exportarEmpleados.controller.js new file mode 100644 index 00000000..f3d3e4c3 --- /dev/null +++ b/Empleados/Controladores/exportarEmpleados.controller.js @@ -0,0 +1,73 @@ +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 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 - Exportar Empleados](https://codeandco-wiki.netlify.app/docs/next/proyectos/textiles/documentacion/requisitos/RF59) + */ +exports.exportarEmpleados = async (req, res) => { + try { + 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({ + mensaje: MENSAJES_EMPLEADOS.EMPLEADOS_NO_ENCONTRADOS.mensaje, + csv: '' + }); + } + + 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' } + ]; + + 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: csvConBOM + }); + } 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/repositorioActualizarGrupo.js b/Empleados/Datos/Repositorios/repositorioActualizarGrupo.js new file mode 100644 index 00000000..4f803667 --- /dev/null +++ b/Empleados/Datos/Repositorios/repositorioActualizarGrupo.js @@ -0,0 +1,111 @@ +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. + * + * 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. 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, + idGrupoEmpleado, + nombre, + descripcion, + ]); + + // 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); + } + + // Eliminar empleados que no están en la nueva lista + await correrQuery( + CONSULTAS.ELIMINAR_EMPLEADOS_DE_GRUPO_BASE.replace('__ID__', idGrupoEmpleado).replace( + '__EMPLEADOS__', + empleadosSTR, + ), + ); + + // 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]); + } + } + + // 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], + ); + + if (resultadoVerificacion[0].validos !== setsDeProductos.length) { + throw new Error(MENSAJES.ERROR_VERIFICACION_CLIENTE_SET.mensaje); + } + + // Eliminar sets que no están en la nueva lista + await correrQuery( + CONSULTAS.ELIMINAR_SETS_DE_GRUPO_BASE.replace('__ID__', idGrupoEmpleado).replace( + '__SETS__', + setsSTR, + ), + ); + + // 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/Empleados/Datos/Repositorios/repositorioCrearEmpleado.js b/Empleados/Datos/Repositorios/repositorioCrearEmpleado.js new file mode 100644 index 00000000..ed0ed8f2 --- /dev/null +++ b/Empleados/Datos/Repositorios/repositorioCrearEmpleado.js @@ -0,0 +1,156 @@ +const db = require('@altertex/util/bd/db'); +const ROL_PREDETERMINADO = 3; // ID del rol por defecto para empleados + +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] + +/** + * Crea un nuevo empleado y su usuario asociado en la base de datos. + * + * @async + * @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.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 conn.beginTransaction(); + + // Validar correo duplicado + const [correoExistente] = await conn.query( + CONSULTAS_IMPORTAR_EMPLEADOS.VALIDAR_CORREOS_DUPLICADOS, + [[empleado.correoElectronico]] + ); + if (correoExistente.length > 0) { + 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 -= 1; + } + 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, + [[empleado.numeroTelefono]] + ); + if (telefonoExistente.length > 0) { + 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_EMPLEADOS.INSERTAR_USUARIO, [ + empleado.nombreCompleto, + empleado.correoElectronico, + empleado.contrasenia, + empleado.numeroTelefono, + empleado.direccion, + empleado.fechaNacimiento, + empleado.genero, + empleado.estatus, + ]); + const idUsuario = resultadoUsuario.insertId; + + // Insertar rol + 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_EMPLEADOS.INSERTAR_USUARIO_CLIENTE, [idUsuario, idCli]); + } + + // Insertar empleado + await conn.query(CONSULTAS_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(`${err.message}`); + } finally { + if (conn) conn.release(); + } +}; diff --git a/Empleados/Datos/Repositorios/repositorioExportarEmpleado.js b/Empleados/Datos/Repositorios/repositorioExportarEmpleado.js new file mode 100644 index 00000000..f8d7f19a --- /dev/null +++ b/Empleados/Datos/Repositorios/repositorioExportarEmpleado.js @@ -0,0 +1,17 @@ +const correrQuery = require('@altertex/util/ser/correrQuery'); +const CONSULTAS_EMPLEADOS = require('@altertex/util/const/consultasEmpleados'); + +/** + * 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 = (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/Datos/Repositorios/repositorioGrupoDeEmpleados.js b/Empleados/Datos/Repositorios/repositorioGrupoDeEmpleados.js index 184fc061..c4975405 100644 --- a/Empleados/Datos/Repositorios/repositorioGrupoDeEmpleados.js +++ b/Empleados/Datos/Repositorios/repositorioGrupoDeEmpleados.js @@ -3,7 +3,7 @@ * @description * Contiene funciones para consultar y validar grupos de empleados asociados a un cliente. * Incluye lógica para obtener todos los grupos de un cliente y verificar duplicados por nombre. - * + * * RF22 - Consulta Lista de Grupo Empleados - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF22 */ @@ -29,6 +29,7 @@ exports.obtenerGrupoDeEmpleados = async (idCliente) => { try { const gruposDeEmpleados = await correrQuery(query, [idCliente]); + return gruposDeEmpleados; } catch { return []; @@ -55,7 +56,7 @@ exports.existeGrupoConNombre = (nombreGrupo, idCliente) => { (err, resultados) => { if (err) return reject(err); resolve(resultados.length > 0); - }, + } ); }); -}; \ No newline at end of file +}; 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/Empleados/Datos/Repositorios/repositorioLeerGrupoDeEmpleados.js b/Empleados/Datos/Repositorios/repositorioLeerGrupoDeEmpleados.js index 9ff8be9a..54831892 100644 --- a/Empleados/Datos/Repositorios/repositorioLeerGrupoDeEmpleados.js +++ b/Empleados/Datos/Repositorios/repositorioLeerGrupoDeEmpleados.js @@ -19,13 +19,37 @@ exports.obtenerGrupoEmpleadosPorId = async (idGrupo) => { if (resultado.length === 0) return null; + // ✅ 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( + setProductosValidos.reduce((acc, obj) => { + acc[obj.id] = obj; // sobrescribe si ya existe ese id + return acc; + }, {}) + ); + const grupoEmpleados = { idGrupo: resultado[0].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) + : [], + empleadosActualizar: (resultado[0].empleadosActualizar || []).filter( + (obj) => obj.id !== null + ), + setProductosActualizar: setProductosFiltrados, // Ya no necesita validación adicional }; + return grupoEmpleados; } catch (error) { console.error('Error al obtener el grupo de empleados con id:', error); 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/actualizarGrupoEmpleados.routes.js b/Empleados/Rutas/RutasIndividuales/actualizarGrupoEmpleados.routes.js new file mode 100644 index 00000000..d4c530bd --- /dev/null +++ b/Empleados/Rutas/RutasIndividuales/actualizarGrupoEmpleados.routes.js @@ -0,0 +1,118 @@ +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'); +// RF[24] Actualiza grupo empleado - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF24] + +/** + * @swagger + * /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. + * 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(), + autorizarToken, + verificarPermisos(PERMISOS.ACTUALIZAR_GRUPO_EMPLEADOS), + validarYSanitizar, + controlador.actualizarGrupoEmpleados +); + +module.exports = ruteador; diff --git a/Empleados/Rutas/RutasIndividuales/crearEmpleado.routes.js b/Empleados/Rutas/RutasIndividuales/crearEmpleado.routes.js new file mode 100644 index 00000000..f883d48f --- /dev/null +++ b/Empleados/Rutas/RutasIndividuales/crearEmpleado.routes.js @@ -0,0 +1,204 @@ +//RF16 - Crear Empleado - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF16] + +const express = require('express'); +const ruteador = express.Router(); + +const controlador = require('@altertex/emp/ctrl/crearEmpleado.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/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, + limitePeticionesDiarias, + validarYSanitizar, + verificarPermisos(PERMISOS.CREAR_EMPLEADO), + controlador.crearEmpleado +); + +module.exports = ruteador; diff --git a/Empleados/Rutas/RutasIndividuales/exportarEmpleados.routes.js b/Empleados/Rutas/RutasIndividuales/exportarEmpleados.routes.js new file mode 100644 index 00000000..a51ccfe8 --- /dev/null +++ b/Empleados/Rutas/RutasIndividuales/exportarEmpleados.routes.js @@ -0,0 +1,77 @@ +//RF59 - Exportar Empleados- https://codeandco-wiki.netlify.app/docs/next/proyectos/textiles/documentacion/requisitos/RF59 + +/** + * @swagger + * /api/empleados/exportar: + * post: + * 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: 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. + */ + +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(), + 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..7f39b6f5 100644 --- a/Empleados/Rutas/indexEmpleados.routes.js +++ b/Empleados/Rutas/indexEmpleados.routes.js @@ -1,14 +1,16 @@ 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'); 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'); - const rutasActualizarEmpleado = require('@altertex/emp/rutasInd/actualizarEmpleado.routes'); +const rutasActualizarGrupo = require('@altertex/emp/rutasInd/actualizarGrupoEmpleados.routes'); const RUTAS = require('@altertex/util/const/rutas'); @@ -26,7 +28,12 @@ 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); +//RF24 - Actualizar Grupo de Empleados - https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF24 +ruteador.use(RUTAS.EMPLEADOS.BASE, rutasActualizarGrupo); +//RF59 - Exportar Empleados- https://codeandco-wiki.netlify.app/docs/next/proyectos/textiles/documentacion/requisitos/RF59 +ruteador.use(RUTAS.EMPLEADOS.BASE, rutasExportarEmpleados); +//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/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); diff --git a/Eventos/Controladores/crearEvento.controller.js b/Eventos/Controladores/crearEvento.controller.js new file mode 100644 index 00000000..fa769be6 --- /dev/null +++ b/Eventos/Controladores/crearEvento.controller.js @@ -0,0 +1,74 @@ +// 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} 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 = 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: idClienteNum, + nombre, + descripcion: descripcion && descripcion.trim() !== '' ? descripcion : null, + puntos: puntosNum, + multiplicador: multiplicadorNum, + periodoRenovacion: periodoRenovacion && periodoRenovacion.trim() !== '' ? periodoRenovacion : null, + renovacion: renovacion ? 1 : 0, + }; + + 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 || resultado, + }); + + } 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: error.message || MENSAJES_EVENTOS.ERROR_CREAR_EVENTO.mensaje, + }); + } +}; diff --git a/Eventos/Datos/Repositorios/repositorioCrearEvento.js b/Eventos/Datos/Repositorios/repositorioCrearEvento.js new file mode 100644 index 00000000..55d09b47 --- /dev/null +++ b/Eventos/Datos/Repositorios/repositorioCrearEvento.js @@ -0,0 +1,64 @@ +// RF36 - Crear Evento - [https://codeandco-wiki.netlify.app/docs/next/proyectos/textiles/documentacion/requisitos/RF36] + +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 + * @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 + * @throws {Error} - Error personalizado según el problema encontrado + */ +exports.crearEvento = async ({ + idCliente, + nombre, + descripcion, + puntos, + multiplicador, + periodoRenovacion, + renovacion, +}) => { + try { + const query = CONSULTAS_EVENTOS.CREAR_EVENTO; + + 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/Eventos/Rutas/RutasIndividuales/consultarEvento.routes.js b/Eventos/Rutas/RutasIndividuales/consultarEvento.routes.js index 42da867c..94653d09 100644 --- a/Eventos/Rutas/RutasIndividuales/consultarEvento.routes.js +++ b/Eventos/Rutas/RutasIndividuales/consultarEvento.routes.js @@ -8,7 +8,6 @@ const validarYSanitizar = require('@altertex/util/inter/validarYSanitizar'); 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/Eventos/Rutas/RutasIndividuales/crearEvento.routes.js b/Eventos/Rutas/RutasIndividuales/crearEvento.routes.js new file mode 100644 index 00000000..97feadf0 --- /dev/null +++ b/Eventos/Rutas/RutasIndividuales/crearEvento.routes.js @@ -0,0 +1,85 @@ +// 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 limitePeticionesDiarias = require('@altertex/util/inter/limitePeticiones'); +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, + limitePeticionesDiarias, + 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/Pedidos/Controladores/actualizarPedido.controller.js b/Pedidos/Controladores/actualizarPedido.controller.js new file mode 100644 index 00000000..299ddbd4 --- /dev/null +++ b/Pedidos/Controladores/actualizarPedido.controller.js @@ -0,0 +1,47 @@ +const MENSAJES = require('@altertex/util/const/mensajesPedidos'); +const repositorio = require('@altertex/pedidos/repos/repositorioActualizarPedido'); + +/** + * 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; + 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..002d898a --- /dev/null +++ b/Pedidos/Datos/Repositorios/repositorioActualizarPedido.js @@ -0,0 +1,50 @@ +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] + +/** + * 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) { + 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, + idPago, + idEnvio, + 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..90ba4c2a --- /dev/null +++ b/Pedidos/Rutas/RutasIndividuales/actualizarPedidos.routes.js @@ -0,0 +1,24 @@ +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'); +const validarYSanitizar = require('@altertex/util/inter/validarYSanitizar'); + +// RF[62] - Actualizar Pedido +ruteador.put( + RUTAS.PEDIDOS.ACTUALIZAR_PEDIDO, + revisarApiKey(), + autorizarToken, + limitePeticiones, + verificarPermisos(PERMISOS.ACTUALIZAR_PEDIDO), + validarYSanitizar, + controlador.actualizarPedido +); + +module.exports = ruteador; diff --git a/Pedidos/Rutas/indexPedidos.routes.js b/Pedidos/Rutas/indexPedidos.routes.js index 83334c54..16647e25 100644 --- a/Pedidos/Rutas/indexPedidos.routes.js +++ b/Pedidos/Rutas/indexPedidos.routes.js @@ -2,9 +2,11 @@ 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/Productos/Controladores/crearProducto.controller.js b/Productos/Controladores/crearProducto.controller.js index 8852ef24..f78d11a0 100644 --- a/Productos/Controladores/crearProducto.controller.js +++ b/Productos/Controladores/crearProducto.controller.js @@ -103,11 +103,11 @@ exports.crearProducto = [ // Upload image processing remains unchanged const urlImagenProductoPromise = imagenProducto ? enviarS3({ - Bucket: process.env.AWS_BUCKET_NAME, - Key: `productos/${imagenProducto.originalname}`, - Body: imagenProducto.buffer, - ContentType: imagenProducto.mimetype, - }) + Bucket: process.env.AWS_BUCKET_NAME, + Key: `productos/${imagenProducto.originalname}`, + Body: imagenProducto.buffer, + ContentType: imagenProducto.mimetype, + }) : Promise.resolve(null); // prettier-ignore @@ -176,4 +176,4 @@ exports.crearProducto = [ if (conexion) conexion.release(); } }, -]; \ No newline at end of file +]; 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/Controladores/exportarProductos.controller.js b/Productos/Controladores/exportarProductos.controller.js new file mode 100644 index 00000000..ebdd243c --- /dev/null +++ b/Productos/Controladores/exportarProductos.controller.js @@ -0,0 +1,49 @@ +const repositorio = require('@altertex/pro/repos/repositorioExportarProducto'); +const MENSAJES_PRODUCTOS = require('@altertex/util/const/mensajesProductos'); + +/** + * Controlador para exportar productos seleccionados de un cliente y retornar Excel. + * + * @async + * @function exportarProductos + * @param {Request} req + * @param {Response} res + * @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) => { + 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, + }); + } + + const buffer = await repositorio.generarArchivoExcel(productos); + + res.setHeader('Content-Disposition', 'attachment; filename=productos.xlsx'); + res.setHeader( + 'Content-Type', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + ); + + return res.send(buffer); + } 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/Controladores/importarProductos.controller.js b/Productos/Controladores/importarProductos.controller.js new file mode 100644 index 00000000..35c59d7a --- /dev/null +++ b/Productos/Controladores/importarProductos.controller.js @@ -0,0 +1,141 @@ +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'); +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'); +const { proveedorExiste } = require('@altertex/pro/repos/repositorioValidarProveedor'); + + +/** + * 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. + * + * @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; + + 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: MENSAJE_PRODUCTOS_INVALIDOS}); + } + + const errores = []; + let conexion = null; + + try { + conexion = await db.getConnection(); + await conexion.beginTransaction(); + + for (let im = 0; im < productos.length; im += 1) { + const { producto, variantes } = productos[im]; + const fila = im + 1; + + const errorProducto = validarProductoImportado(producto); + if (errorProducto) { + 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: MENSAJE_VARIANTES_INVALIDAS }); + continue; + } + + for (const variante of variantes) { + const errorVariante = validarVariante(variante); + if (errorVariante) { + errores.push({ fila, error: errorVariante.error }); + continue; + } + + 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: MENSAJE_ERRORES_ARCHIVO, + errores, + }); + } + const generarSKUConsecutivo = crearGeneradorSKUConsecutivo(); + 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) { + const idVariante = await repositorioCrearVariante.crearVariante(idProducto, variante); + + const opcionesConSKU = variante.opciones.map(opcion => ({ + ...opcion, + SKUautomatico: generarSKUConsecutivo( + producto.nombreComun, + variante.nombreVariante, + opcion.valorOpcion || 'SINVALOR' + ) + })); + + await repositorioCrearOpcion.crearOpcion(idVariante, opcionesConSKU); + } + } + + await conexion.commit(); + + return res.status(200).json({ + mensaje: IMPORTACION_EXITOSA, + errores: null, + }); + + } catch (err) { + if (conexion) await conexion.rollback(); + return res.status(500).json({ + mensaje: ERROR_AL_IMPORTAR, + error: err.message, + }); + } finally { + if (conexion) conexion.release(); + } +}; diff --git a/Productos/Controladores/leerProducto.controller.js b/Productos/Controladores/leerProducto.controller.js new file mode 100644 index 00000000..5303e6f6 --- /dev/null +++ b/Productos/Controladores/leerProducto.controller.js @@ -0,0 +1,38 @@ +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.query.idProducto; + const idCliente = req.user.clienteSeleccionado; + + if (!idProducto) { + 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({ + 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/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/Productos/Datos/Repositorios/repositorioExportarProducto.js b/Productos/Datos/Repositorios/repositorioExportarProducto.js new file mode 100644 index 00000000..69b7af2c --- /dev/null +++ b/Productos/Datos/Repositorios/repositorioExportarProducto.js @@ -0,0 +1,135 @@ +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. + * + * @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]); +}; + +/** + * 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, + }; + }); + }); + }); +} 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/Datos/Repositorios/repositorioValidarProveedor.js b/Productos/Datos/Repositorios/repositorioValidarProveedor.js new file mode 100644 index 00000000..7886411c --- /dev/null +++ b/Productos/Datos/Repositorios/repositorioValidarProveedor.js @@ -0,0 +1,19 @@ +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. + * @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( + consultas.VERIFICAR_EXISTE, + [idProveedor] + ); + return result.length > 0; +} + +module.exports = { proveedorExiste }; 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/RutasIndividuales/importarProductos.routes.js b/Productos/Rutas/RutasIndividuales/importarProductos.routes.js new file mode 100644 index 00000000..2c95b954 --- /dev/null +++ b/Productos/Rutas/RutasIndividuales/importarProductos.routes.js @@ -0,0 +1,191 @@ +// 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'); +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 +); + +module.exports = ruteador; 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..3f014e4e 100644 --- a/Productos/Rutas/indexProductos.routes.js +++ b/Productos/Rutas/indexProductos.routes.js @@ -1,8 +1,11 @@ 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 rutaImportarProductos = require('@altertex/pro/rutasInd/importarProductos.routes'); +const rutasLeerProducto = require('@altertex/pro/rutasInd/leerProductos.routes'); +const rutasExportarProductos = require('@altertex/pro/rutasInd/exportarProductos.routes'); const RUTAS = require('@altertex/util/const/rutas'); @@ -10,7 +13,13 @@ 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); +// RF[56] Leer producto - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF56] +ruteador.use(RUTAS.PRODUCTOS.BASE, rutaImportarProductos); +// 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/Roles/Controladores/actualizarRol.controller.js b/Roles/Controladores/actualizarRol.controller.js new file mode 100644 index 00000000..0e129abb --- /dev/null +++ b/Roles/Controladores/actualizarRol.controller.js @@ -0,0 +1,29 @@ +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 datosActualizacion = req.body.datosRolActualizacion; + + if (!datosActualizacion) { + return res.status(MENSAJES.PARAMETROS_INVALIDOS.codigo).json({ mensaje: MENSAJES.PARAMETROS_INVALIDOS.mensaje }); + } + + try { + 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 }); + } +}; \ No newline at end of file diff --git a/Roles/Controladores/consultarDetalleRol.controller.js b/Roles/Controladores/consultarDetalleRol.controller.js new file mode 100644 index 00000000..a402fe08 --- /dev/null +++ b/Roles/Controladores/consultarDetalleRol.controller.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/repositorioActualizarRol.js b/Roles/Datos/Repositorios/repositorioActualizarRol.js new file mode 100644 index 00000000..22e67561 --- /dev/null +++ b/Roles/Datos/Repositorios/repositorioActualizarRol.js @@ -0,0 +1,94 @@ +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 {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 (datosActualizarRol) => { + const datosRol = datosActualizarRol.datosRol; + const idRol = datosActualizarRol.idRol; + const permisos = datosRol.permisos || []; + + if (!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/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/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/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..5f70fd94 100644 --- a/Roles/Rutas/indexRoles.routes.js +++ b/Roles/Rutas/indexRoles.routes.js @@ -21,6 +21,10 @@ const rutasObtenerOpcionesRol = require('@altertex/rol/rutasInd/obtenerOpcionesR 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'); @@ -39,5 +43,10 @@ ruteador.use(RUTAS.ROLES.BASE, rutasObtenerOpcionesRol); 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; +module.exports = ruteador; \ No newline at end of file diff --git a/SetsProductos/Controladores/actualizarSetsProductos.controller.js b/SetsProductos/Controladores/actualizarSetsProductos.controller.js new file mode 100644 index 00000000..ae6d0fcf --- /dev/null +++ b/SetsProductos/Controladores/actualizarSetsProductos.controller.js @@ -0,0 +1,53 @@ +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 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.actualizarSetProductos = async (req, res) => { + const datosActualizacion = req.body; + const cliente = req.user.clienteSeleccionado; + + // 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.actualizarSetProductos(cliente, 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: error.message, + }); + } +}; diff --git a/SetsProductos/Controladores/crearSetsProductos.controller.js b/SetsProductos/Controladores/crearSetsProductos.controller.js new file mode 100644 index 00000000..96fc79a7 --- /dev/null +++ b/SetsProductos/Controladores/crearSetsProductos.controller.js @@ -0,0 +1,49 @@ +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.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 }); + } + + 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/repositorioActualizarSetsProductos.js b/SetsProductos/Datos/Repositorios/repositorioActualizarSetsProductos.js new file mode 100644 index 00000000..0e91e086 --- /dev/null +++ b/SetsProductos/Datos/Repositorios/repositorioActualizarSetsProductos.js @@ -0,0 +1,75 @@ +const correrQuery = require('@altertex/util/ser/correrQuery'); +const MENSAJES = require('@altertex/util/const/mensajesSetsProductos'); +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. + * + * @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; + + try { + + const duplicados = await correrQuery(CONSULTAS.CONSULTAR_NOMBRE_DUPLICADO, [ + idCliente, + idSetProducto, + 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]); + + // 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/Datos/Repositorios/repositorioCrearSetsProductos.js b/SetsProductos/Datos/Repositorios/repositorioCrearSetsProductos.js new file mode 100644 index 00000000..21623f34 --- /dev/null +++ b/SetsProductos/Datos/Repositorios/repositorioCrearSetsProductos.js @@ -0,0 +1,61 @@ +const MENSAJES_SETS_PRODUCTOS = require('@altertex/util/const/mensajesSetsProductos'); +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 { + + 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 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; + + + 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/actualizarSetsProductos.routes.js b/SetsProductos/Rutas/RutasIndividuales/actualizarSetsProductos.routes.js new file mode 100644 index 00000000..d2f5892b --- /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.actualizarSetProductos +); + +module.exports = ruteador; 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/SetsProductos/Rutas/RutasIndividuales/crearSetsProductos.routes.js b/SetsProductos/Rutas/RutasIndividuales/crearSetsProductos.routes.js new file mode 100644 index 00000000..22a05c6b --- /dev/null +++ b/SetsProductos/Rutas/RutasIndividuales/crearSetsProductos.routes.js @@ -0,0 +1,89 @@ +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'); + +/** + * @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 diff --git a/SetsProductos/Rutas/indexSetsProductos.routes.js b/SetsProductos/Rutas/indexSetsProductos.routes.js index a30971e5..b1f8e1c2 100644 --- a/SetsProductos/Rutas/indexSetsProductos.routes.js +++ b/SetsProductos/Rutas/indexSetsProductos.routes.js @@ -2,6 +2,8 @@ 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 rutasActualizarSetsProductos = require('@altertex/setspro/rutasInd/actualizarSetsProductos.routes'); const RUTAS = require('@altertex/util/const/rutas'); @@ -11,4 +13,9 @@ 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); + +//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/Usuarios/Controladores/actualizarUsuario.controller.js b/Usuarios/Controladores/actualizarUsuario.controller.js new file mode 100644 index 00000000..e6538745 --- /dev/null +++ b/Usuarios/Controladores/actualizarUsuario.controller.js @@ -0,0 +1,45 @@ +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] + +/** + * 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) => { + const cambios = req.body.cambios || req.body; + + 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[0].contrasenia) { + const contraseniaEncriptada = await bcrypt.hash(datos[0].contrasenia, 10); + datos[0].contrasenia = contraseniaEncriptada; + } + + try { + await repositorio.actualizarUsuario(datos); + return res.status(200).json({ mensaje: 'Usuario actualizado correctamente' }); + } catch (error) { + return res.status(400).json({ mensaje: error.message }); + } +}; 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/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 diff --git a/Usuarios/Datos/Repositorios/repositorioActualizarUsuario.js b/Usuarios/Datos/Repositorios/repositorioActualizarUsuario.js new file mode 100644 index 00000000..8100363a --- /dev/null +++ b/Usuarios/Datos/Repositorios/repositorioActualizarUsuario.js @@ -0,0 +1,104 @@ +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(MENSAJES.ERROR_OBTENER_USUARIO.mensaje); + } + + try { + for (const usuario of datos) { + const { + idUsuario, + correoElectronico, + nombreCompleto, + contrasenia, + numeroTelefono, + direccion, + fechaNacimiento, + idRol, + genero, + estatus, + cliente, + } = usuario; + + const resultadoCorreo = await correrQuery( + CONSULTAS_USUARIOS.VALIDAR_CORREO_DUPLICADO_ACTUALIZACION, + [correoElectronico, idUsuario] + ); + + if (resultadoCorreo.length > 0) { + throw new Error(MENSAJES.USUARIO_YA_EXISTE.mensaje); + } + + const conContrasena = contrasenia && contrasenia.trim() !== ''; + + 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, + ]); + } + + // Asociar cliente(s) + if (cliente) { + const clientes = Array.isArray(cliente) ? cliente : [cliente]; + + // Eliminar asociaciones anteriores + await correrQuery(CONSULTAS_USUARIOS.ELIMINAR_USUARIO_CLIENTE, [idUsuario]); + + for (const idCliente of clientes) { + if (idCliente) { + await correrQuery(CONSULTAS_USUARIOS.ASOCIAR_USUARIO_A_CLIENTE, [ + idUsuario, + idCliente, + ]); + } + } + } + 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/Usuarios/Rutas/RutasIndividuales/actualizarUsuario.routes.js b/Usuarios/Rutas/RutasIndividuales/actualizarUsuario.routes.js new file mode 100644 index 00000000..d3239e98 --- /dev/null +++ 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/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/Usuarios/Rutas/indexUsuarios.routes.js b/Usuarios/Rutas/indexUsuarios.routes.js index 5dc5ed19..f6fa16d0 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'); @@ -11,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/consultasCategorias.js b/Utilidades/Constantes/consultasCategorias.js index 6890d77a..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: ` @@ -47,4 +46,32 @@ 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 = ?; + `, + + 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/consultasCuotas.js b/Utilidades/Constantes/consultasCuotas.js index be725c22..3308c3d1 100644 --- a/Utilidades/Constantes/consultasCuotas.js +++ b/Utilidades/Constantes/consultasCuotas.js @@ -40,6 +40,44 @@ 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 = ?; + `, + + 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/consultasEmpleados.js b/Utilidades/Constantes/consultasEmpleados.js index 38e3b382..fa19ab29 100644 --- a/Utilidades/Constantes/consultasEmpleados.js +++ b/Utilidades/Constantes/consultasEmpleados.js @@ -24,4 +24,58 @@ 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, + CASE + WHEN u.estatus = 1 THEN 'Activo' + WHEN u.estatus = 0 THEN 'Inactivo' + ELSE 'Desconocido' + END AS 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__); + `, + 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; + `, + + 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 (?, ?) + `, + }; 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/consultasGrupoEmpleados.js b/Utilidades/Constantes/consultasGrupoEmpleados.js index a5919454..ffdd2b33 100644 --- a/Utilidades/Constantes/consultasGrupoEmpleados.js +++ b/Utilidades/Constantes/consultasGrupoEmpleados.js @@ -1,54 +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, - 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 + 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, + 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 + 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 != ?); + `, + 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 + `, + 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__; + `, VALIDAR_NOMBRE_REPETIDO: ` - SELECT 1 FROM grupo_empleado - WHERE idCliente = ? AND nombre = ? LIMIT 1 + SELECT 1 + FROM grupo_empleado + WHERE idCliente = ? + AND nombre = ? LIMIT 1 `, CREAR_GRUPO: ` - INSERT INTO grupo_empleado (idCliente, nombre, descripcion) VALUES (?, ?, ?); + INSERT INTO grupo_empleado (idCliente, nombre, descripcion) + VALUES (?, ?, ?); `, ASIGNAR_EMPLEADO_A_GRUPO: ` - INSERT INTO empleado_grupo (idEmpleado, idGrupo) VALUES (?, ?); - ` -}; + 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 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/consultasPedidos.js b/Utilidades/Constantes/consultasPedidos.js index a92f3cfc..1426a12d 100644 --- a/Utilidades/Constantes/consultasPedidos.js +++ b/Utilidades/Constantes/consultasPedidos.js @@ -34,4 +34,16 @@ module.exports = { ELIMINAR_PEDIDO: ` DELETE FROM pedido WHERE idPedido = ?;`, + + ACTUALIZAR_PEDIDO: ` + 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 = ?; + `, }; diff --git a/Utilidades/Constantes/consultasProductos.js b/Utilidades/Constantes/consultasProductos.js index 81b5df4c..b3526c61 100644 --- a/Utilidades/Constantes/consultasProductos.js +++ b/Utilidades/Constantes/consultasProductos.js @@ -2,28 +2,124 @@ 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 ( - 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 (?)", + + 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 (?); + `, + + 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.nombreCompania, + '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 = ?; + `, + OBTENER_DATOS_EXPORTACION: ` + SELECT + p.idProducto, + p.idProveedor, + p.nombreComun AS nombreProducto, + p.nombreComercial, + p.descripcion AS descripcionProducto, + p.tipoProducto, + p.marca, + p.modelo, + p.costo, + p.precioVenta, + p.precioCliente, + p.precioPuntos, + p.impuesto, + p.descuento, + p.estado, + p.envio, + GROUP_CONCAT( + CONCAT( + v.nombreVariante, '-', + v.descripcion, ',', + (SELECT GROUP_CONCAT( + CONCAT(o.valorOpcion, ':', o.SKUcomercial, ':', o.SKUautomatico, ':', 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; + `, }; 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 + ` +} + diff --git a/Utilidades/Constantes/consultasRoles.js b/Utilidades/Constantes/consultasRoles.js index 01d1fc37..3f6de048 100644 --- a/Utilidades/Constantes/consultasRoles.js +++ b/Utilidades/Constantes/consultasRoles.js @@ -28,38 +28,64 @@ 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 = ?; `, + VERIFICAR_NOMBRE_DUPLICADO_ROL: ` + select * + from rol + where nombre = ?; + `, -}; +}; \ No newline at end of file diff --git a/Utilidades/Constantes/consultasSetsProductos.js b/Utilidades/Constantes/consultasSetsProductos.js index 741e9a16..07a20c80 100644 --- a/Utilidades/Constantes/consultasSetsProductos.js +++ b/Utilidades/Constantes/consultasSetsProductos.js @@ -1,30 +1,78 @@ 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, + 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 + 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 = ?); + `, + CONSULTAR_NOMBRE_DUPLICADO: ` + SELECT idSetProducto + FROM set_producto + WHERE idCliente = ? and idSetProducto!= ? + AND (nombre = ?); + `, + CONSULTAR_PRODUCTOS_EXISTENTES: ` + SELECT idProducto + FROM producto + WHERE idProducto IN (__IDS__); + `, + ACTUALIZAR_SET_INFO: ` + UPDATE set_producto + SET nombre = ?, activo = ?, descripcion = ? + 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/consultasUsuarios.js b/Utilidades/Constantes/consultasUsuarios.js index 0f679e91..e2062013 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 (?, ?); @@ -54,8 +59,9 @@ module.exports = { u.fechaNacimiento, u.genero, u.estatus, - r.nombre AS rol, - uc.idCliente, + r.idRol AS rol, + r.nombre AS nombreRol, + uc.idCliente AS idCliente, c.nombreComercial AS nombreCliente FROM usuario u LEFT JOIN usuario_rol ur ON u.idUsuario = ur.idUsuario @@ -66,29 +72,60 @@ 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 FROM usuario WHERE idUsuario = (?); `, + + ACTUALIZAR_DATOS_USUARIO: ` + UPDATE usuario SET + nombreCompleto = ?, + correoElectronico = ?, + contrasenia = ?, + numeroTelefono = ?, + direccion = ?, + fechaNacimiento = ?, + genero = ?, + estatus = ? + 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 = ?; + `, + VALIDAR_CORREO: ` SELECT idUsuario FROM usuario @@ -183,6 +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/mensajesCategorias.js b/Utilidades/Constantes/mensajesCategorias.js index 4b860b8e..3629a3f0 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, @@ -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.', @@ -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.', + }, + }; 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/mensajesCuotas.js b/Utilidades/Constantes/mensajesCuotas.js index 9ee05ba0..9ac28661 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,20 @@ 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.', + }, + + 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/mensajesEmpleados.js b/Utilidades/Constantes/mensajesEmpleados.js index f67fd574..dcb3049b 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,18 @@ module.exports = { codigo: 400, mensaje: 'Error al actualizar', }, + ERROR_EXPORTAR_EMPLEADOS: { + codigo: 400, + mensaje: 'Error al exportar la lista de empleados.', + }, + ERROR_CREAR: { + codigo: 400, + mensaje: 'Error al crear', + }, + CAMPOS_REQUERIDOS: { + codigo: 400, + mensaje: 'Faltan campos requeridos', + }, // 403 - Forbidden PERMISO_DENEGADO: { @@ -50,6 +66,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 +97,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_CREACION: { + codigo: 500, + mensaje: 'Error al crear el empleado', + }, }; diff --git a/Utilidades/Constantes/mensajesEventos.js b/Utilidades/Constantes/mensajesEventos.js index 60bb5ef7..78590e3b 100644 --- a/Utilidades/Constantes/mensajesEventos.js +++ b/Utilidades/Constantes/mensajesEventos.js @@ -1,23 +1,11 @@ module.exports = { // 201 - Creado - CATEGORIA_CREADA: { - codigo: 201, - mensaje: 'Categoría creada correctamente.', - }, EVENTO_CREADO: { codigo: 201, mensaje: 'Evento creado correctamente.', }, // 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.', @@ -58,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.', @@ -70,41 +54,19 @@ 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.', + // 400 - Error de cliente + ERROR_CLIENTE_NO_EXISTE: { + codigo: 400, + mensaje: 'El cliente especificado no existe en el sistema.', }, + // 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.', @@ -125,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/Utilidades/Constantes/mensajesGrupoEmpleados.js b/Utilidades/Constantes/mensajesGrupoEmpleados.js index b1530e8b..1e3dd0ef 100644 --- a/Utilidades/Constantes/mensajesGrupoEmpleados.js +++ b/Utilidades/Constantes/mensajesGrupoEmpleados.js @@ -51,4 +51,24 @@ 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.', + }, + ERROR_VERIFICACION_CLIENTE_SET: { + codigo: 400, + mensaje: 'Algunos sets de productos no pertenecen al cliente de este grupo.', + }, }; 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/mensajesProductos.js b/Utilidades/Constantes/mensajesProductos.js index 8f357e88..7f713977 100644 --- a/Utilidades/Constantes/mensajesProductos.js +++ b/Utilidades/Constantes/mensajesProductos.js @@ -14,6 +14,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: { @@ -63,12 +67,37 @@ 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.', + }, + ERROR_EXPORTACION: { + codigo: 400, + mensaje: 'Error al exportar la lista de productos.', }, }; 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/mensajesSetsProductos.js b/Utilidades/Constantes/mensajesSetsProductos.js index f887f58c..3d8291c9 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,56 @@ 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.', + }, + 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.', + }, + ERROR_ACTUALIZAR_SET_PRODUCTOS: { + 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', + }, + PARAMETROS_INVALIDOS: { + codigo: 400, + mensaje: 'Parámetros inválidos', + }, }; diff --git a/Utilidades/Constantes/mensajesUsuarios.js b/Utilidades/Constantes/mensajesUsuarios.js index ac2ddeee..9e01f97d 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: { @@ -51,6 +55,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,12 +87,16 @@ 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.', }, + ERROR_ACTUALIZAR_USUARIO: { + 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.', + }, }; diff --git a/Utilidades/Constantes/rutas.js b/Utilidades/Constantes/rutas.js index 92946212..66bf6d91 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', @@ -25,7 +26,8 @@ module.exports = { CONSULTAR_LISTA_USUARIOS: '/consultar-lista-usuarios', CREAR: '/crear', ELIMINAR_USUARIOS: '/eliminar-usuarios', - LEER: '/consultar-usuario', + LEER: '/leer', + ACTUALIZAR: '/actualizar', }, EVENTOS: { BASE: '/eventos', @@ -40,6 +42,9 @@ module.exports = { CONSULTAR_LISTA: '/consultar-lista', CREAR: '/crear', ELIMINAR_PRODUCTO: '/eliminar', + IMPORTAR: '/importar', + LEER: '/leer-producto', + EXPORTAR_PRODUCTOS: '/exportar-productos', }, PROVEEDORES: { BASE: '/proveedores', @@ -52,6 +57,7 @@ module.exports = { CREAR: '/crear', SUBIR_IMAGEN: '/subir-imagen', ELIMINAR_SET_PRODUCTOS: '/eliminar', + ACTUALIZAR: '/actualizar', }, CLIENTES: { BASE: '/clientes', @@ -64,14 +70,17 @@ module.exports = { }, EMPLEADOS: { BASE: '/empleados', + CREAR: '/crear', CONSULTAR_LISTA: '/consultar-lista', CONSULTAR_GRUPO: '/consultar-grupo', 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', + ACTUALIZAR_GRUPO_EMPLEADO: '/actualizar-grupo', }, CUOTAS: { BASE: '/cuotas', @@ -79,6 +88,8 @@ module.exports = { OPCIONES: '/obtener-opciones', 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', @@ -87,11 +98,14 @@ module.exports = { OBTENER_OPCIONES: '/obtener-opciones', CONFIRMAR_CREACION: '/confirmar-creacion', ELIMINAR_ROL: '/eliminar', + LEER_ROL: '/leer', + ACTUALIZAR: '/actualizar-rol', }, PEDIDOS: { BASE: '/pedidos', CONSULTAR_LISTA: '/consultar-lista', ELIMINAR_PEDIDO: '/eliminar', + ACTUALIZAR_PEDIDO: '/actualizar-pedido', }, PAGOS: { BASE: '/pagos', diff --git a/Utilidades/Intermediarios/Validaciones/validarOpcionesImportar.js b/Utilidades/Intermediarios/Validaciones/validarOpcionesImportar.js new file mode 100644 index 00000000..69e7775e --- /dev/null +++ b/Utilidades/Intermediarios/Validaciones/validarOpcionesImportar.js @@ -0,0 +1,81 @@ +//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) + || opcion.cantidad % 1 !== 0 + ) { + 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.SKUcomercial + || 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..1fd8e2b3 --- /dev/null +++ b/Utilidades/Intermediarios/Validaciones/validarProductoImportado.js @@ -0,0 +1,181 @@ +//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 + || producto.idProveedor % 1 !== 0 + ) + ) { + 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: 'nombreProducto es requerido, debe ser una cadena de texto y no exceder 100 caracteres.', + }; + } + + if ( + !producto.nombreComercial + || typeof producto.nombreComercial !== 'string' + || producto.nombreComercial.length > 100 + ) { + return { + error: 'nombreComercial es requerido, debe ser una cadena de texto y no exceder 100 caracteres.', + }; + } + + if ( + producto.descripcion !== null + && (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.', + }; + } + + if ( + producto.marca !== null + && (typeof producto.marca !== 'string' || producto.marca.length > 100 || producto.marca.trim() === '') + ) { + 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 y no exceder 100 caracteres.' }; + } + + if ( + producto.tipoProducto !== null + && (typeof producto.tipoProducto !== 'string' || producto.tipoProducto.length > 50 || producto.tipoProducto.trim() === '') + ) { + return { + error: 'tipoProducto debe ser una cadena de texto y no exceder 50 caracteres.', + }; + } + + if ( + 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 ( + 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 ( + typeof producto.precioCliente !== 'number' + || producto.precioCliente < 0 + || Number.isNaN(producto.precioCliente) + ) { + return { + error: 'precioCliente debe ser un número mayor o igual a cero', + }; + } + + if ( + 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 ( + typeof producto.impuesto !== 'number' + || producto.impuesto < 0 + || Number.isNaN(producto.impuesto) + ) { + return { + error: 'impuesto debe ser un número mayor o igual a cero', + }; + } + + if ( + 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 y menor o igual a 100', + }; + } + + 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/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.', }; } diff --git a/Utilidades/Intermediarios/Validaciones/validarVarianteImportar.js b/Utilidades/Intermediarios/Validaciones/validarVarianteImportar.js new file mode 100644 index 00000000..a65542f0 --- /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: 'descripcionVariante debe ser una cadena de texto y no exceder 1000 caracteres.', + }; + } + + return null; +}; diff --git a/Utilidades/Intermediarios/generarSKUAuto.js b/Utilidades/Intermediarios/generarSKUAuto.js new file mode 100644 index 00000000..d8ffb993 --- /dev/null +++ b/Utilidades/Intermediarios/generarSKUAuto.js @@ -0,0 +1,66 @@ +/** + * 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 { + 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 +}; 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_/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 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/_tests_/Eventos/Controladores/crearEvento.controller.test.js b/_tests_/Eventos/Controladores/crearEvento.controller.test.js new file mode 100644 index 00000000..68c0613d --- /dev/null +++ b/_tests_/Eventos/Controladores/crearEvento.controller.test.js @@ -0,0 +1,185 @@ +// 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 requeridos vacíos', async () => { + // Arrange - Solo faltan los campos requeridos + req.body = { + idCliente: '', + nombre: '', + descripcion: '', // Opcional - está bien que esté vacío + puntos: '', + multiplicador: '', + periodoRenovacion: '', // Opcional - está bien que esté vacío + renovacion: false, + }; + + // 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: 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, + }); + }); + + 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.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_CLIENTE_NO_EXISTE.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/_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/_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/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/dump.rdb b/dump.rdb new file mode 100644 index 00000000..fa5c9e72 Binary files /dev/null and b/dump.rdb differ diff --git a/jest.config.js b/jest.config.js index ae7116a4..9162afb7 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', @@ -60,7 +76,28 @@ 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", + + // 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', + + // Sets de productos + "^@altertex/setspro/ctrl/(.*)$": "/SetsProductos/Controladores/$1", + "^@altertex/setspro/repos/(.*)$": "/SetsProductos/Datos/Repositorios/$1", + // Generic mapping as fallback '^@altertex/(.*)$': '/$1', + }, }; diff --git a/package-lock.json b/package-lock.json index 23c6c96a..ce03fdc1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,10 +19,14 @@ "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", "express": "^4.21.2", "helmet": "^8.1.0", - "jest": "^29.7.0", + "i": "^0.3.7", + "json2csv": "^6.0.0-alpha.2", "jsonwebtoken": "^9.0.2", "module-alias": "^2.2.3", "multer": "^1.4.5-lts.1", @@ -42,13 +46,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 +1155,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 +1170,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 +1180,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 +1211,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 +1229,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 +1246,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 +1263,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 +1280,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 +1290,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 +1304,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 +1322,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 +1332,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 +1342,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 +1352,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 +1362,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 +1376,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 +1392,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 +1405,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 +1418,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 +1431,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 +1447,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 +1463,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 +1476,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 +1489,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 +1505,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 +1518,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 +1531,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 +1544,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 +1557,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 +1570,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 +1583,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 +1599,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 +1615,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 +1652,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 +1667,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 +1686,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 +1704,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 +1714,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 +1735,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": { @@ -1894,6 +1941,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", @@ -1964,6 +2052,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 +2069,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 +2079,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 +2093,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 +2107,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 +2120,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 +2136,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 +2149,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 +2159,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 +2176,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 +2194,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 +2242,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 +2258,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 +2272,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 +2285,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 +2303,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 +2319,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 +2363,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 +2376,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 +2391,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 +2407,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 +2423,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 +2450,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 +2468,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 +2483,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 +2493,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 +2503,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 +3219,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 +3236,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" @@ -3840,14 +3960,19 @@ "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", - "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 +3980,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 +4035,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 +4051,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 +4067,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 +4083,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 +4099,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 +4114,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 +4130,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 +4146,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 +4163,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 +4179,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 +4197,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 +4324,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 +4471,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", @@ -4442,6 +4567,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 +4581,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 +4591,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 +4602,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 +4619,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 +4638,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 +4655,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 +4671,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 +4690,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 +4722,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 +4732,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 +4825,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" @@ -4748,6 +4886,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", @@ -4879,6 +5070,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 +5092,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 +5109,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 +5126,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 +5136,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 +5152,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 +5179,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", @@ -5044,6 +5242,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", @@ -5056,6 +5276,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", @@ -5068,6 +5337,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", @@ -5130,6 +5405,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 +5438,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" @@ -5178,6 +5455,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", @@ -5190,6 +5476,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", @@ -5267,6 +5570,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 +5589,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", @@ -5301,10 +5606,23 @@ ], "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", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -5321,6 +5639,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 +5721,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 +5737,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 +5774,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 +5797,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 +5808,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": { @@ -5552,6 +5876,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", @@ -5598,6 +5951,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": { @@ -5673,13 +6027,53 @@ "node": ">= 0.10" } }, - "node_modules/create-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", - "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", + "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", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", "chalk": "^4.0.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", @@ -5704,6 +6098,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", @@ -5714,12 +6109,93 @@ "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", + "license": "MIT", + "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", @@ -5735,6 +6211,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", @@ -5763,6 +6248,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 +6356,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 +6376,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" @@ -5956,6 +6444,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", @@ -5975,12 +6472,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" @@ -6004,6 +6503,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", @@ -6020,6 +6528,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 +6583,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" @@ -6408,10 +6918,54 @@ "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", "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, "license": "MIT", "dependencies": { "cross-spawn": "^7.0.3", @@ -6435,6 +6989,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 +6998,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", @@ -6535,6 +7091,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", @@ -6552,6 +7121,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 +7176,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" @@ -6800,6 +7371,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", @@ -6820,6 +7397,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", @@ -6842,6 +7435,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 +7478,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 +7501,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" @@ -7149,6 +7745,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,11 +7840,20 @@ "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" } }, + "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", @@ -7282,6 +7888,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", @@ -7312,6 +7924,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 +7944,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 +8054,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 +8128,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 +8218,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 +8252,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 +8269,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 +8286,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 +8301,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 +8316,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 +8334,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 +8355,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 +8382,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 +8397,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 +8429,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 +8463,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 +8509,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 +8525,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 +8538,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 +8555,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 +8573,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 +8583,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 +8609,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 +8623,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 +8639,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 +8660,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 +8675,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 +8693,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 +8703,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 +8724,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 +8738,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 +8771,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 +8805,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 +8837,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 +8855,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 +8873,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 +8886,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 +8906,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 +8922,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 +8999,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 +9019,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": { @@ -8388,10 +9043,36 @@ "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", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, "license": "MIT", "bin": { "json5": "lib/cli.js" @@ -8428,6 +9109,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", @@ -8463,6 +9162,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" @@ -8477,10 +9177,23 @@ "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", "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -8500,12 +9213,28 @@ "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", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "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", @@ -8534,6 +9263,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", @@ -8541,6 +9294,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", @@ -8560,12 +9319,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", @@ -8584,6 +9355,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", @@ -8603,6 +9380,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", @@ -8639,6 +9428,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 +9453,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 +9469,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 +9506,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 +9522,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 +9569,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 +9746,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 +9904,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 +9999,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 +10079,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 +10144,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 +10287,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 +10340,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 +10362,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 +10393,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 +10413,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 +10426,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 +10440,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 +10453,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 +10469,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 +10764,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 +10779,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 +10825,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 +10955,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", @@ -10378,6 +11192,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", @@ -10498,6 +11320,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": { @@ -10573,6 +11396,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", @@ -10589,6 +11442,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", @@ -10767,6 +11621,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 +11634,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 +11654,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" @@ -10812,6 +11669,24 @@ "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/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", @@ -10956,6 +11831,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", @@ -11086,6 +11973,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", @@ -11109,6 +12002,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 +12015,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" @@ -11133,9 +12028,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", @@ -11236,12 +12131,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 +12218,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 +12291,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 +12304,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 +12370,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 +12410,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 +12420,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 +12430,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" @@ -11640,18 +12544,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 +12632,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 +12667,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", @@ -11841,10 +12745,41 @@ "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", "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, "license": "ISC", "dependencies": { "@istanbuljs/schema": "^0.1.2", @@ -11855,10 +12790,20 @@ "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", "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, "license": "BSD-3-Clause" }, "node_modules/to-regex-range": { @@ -11897,6 +12842,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", @@ -11948,6 +12902,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", @@ -11993,6 +12955,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 +12965,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" @@ -12038,6 +13002,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", @@ -12048,6 +13023,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": { @@ -12065,10 +13041,29 @@ "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", "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, "funding": [ { "type": "opencollective", @@ -12185,6 +13180,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 +13237,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 +13263,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 +13316,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 +13340,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", @@ -12408,6 +13408,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", @@ -12421,6 +13427,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 +13437,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 +13453,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 +13472,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 +13482,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" @@ -12516,6 +13527,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 a503511e..f8296d10 100644 --- a/package.json +++ b/package.json @@ -23,10 +23,14 @@ "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", "express": "^4.21.2", "helmet": "^8.1.0", - "jest": "^29.7.0", + "i": "^0.3.7", + "json2csv": "^6.0.0-alpha.2", "jsonwebtoken": "^9.0.2", "module-alias": "^2.2.3", "multer": "^1.4.5-lts.1", @@ -46,7 +50,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": ".",