From 7b8010cae1005a02db50d6a449abc7126c6f2f3f Mon Sep 17 00:00:00 2001 From: Juan Orozco | JDCodeWork Date: Fri, 3 Oct 2025 15:10:56 -0500 Subject: [PATCH 1/5] Reto 4001 completado --- 4001_juan-orozco_v1.js | 135 +++++++++++++++++++++++++++++++++++++++++ package.json | 20 ++++++ 2 files changed, 155 insertions(+) create mode 100644 4001_juan-orozco_v1.js create mode 100644 package.json diff --git a/4001_juan-orozco_v1.js b/4001_juan-orozco_v1.js new file mode 100644 index 0000000..17847f9 --- /dev/null +++ b/4001_juan-orozco_v1.js @@ -0,0 +1,135 @@ +import fs from 'fs' +import path from 'path' + +class LocalDB { + constructor() { + const dirName = path.dirname(new URL(import.meta.url).pathname) + + this.filePath = path.join(dirName, 'users.json') + this.tempFilePath = path.join(dirName, 'users_temp.json') + + this.data = this.loadData() + } + + loadData() { + if (!fs.existsSync(this.filePath)) { + fs.writeFileSync(this.filePath, JSON.stringify([])) + } + + const data = fs.readFileSync(this.filePath, 'utf-8') + return JSON.parse(data) + } + + saveData() { + fs.writeFileSync(this.tempFilePath, JSON.stringify(this.data)) + fs.renameSync(this.tempFilePath, this.filePath) + } +} + +function addUser() { + const db = new LocalDB() + + const [id, name, email, role] = process.argv.slice(3).join(' ').split(',') + + if (!id || !name || !email || !role) { + console.error('Faltan argumentos. Uso: node 4001_juan-orozco_v1.js ADD ,,,') + return + } + + if (db.data.find(user => user.id === id)) { + console.error('El identificador ya existe. Intente con otro.') + return + } + + // Se pone esta restricción para evitar nombres muy largos en la tabla + if (name.length >= 15) { + console.error('El nombre no debe exceder los 15 caracteres.') + return + } + + if (db.data.find(user => user.email === email)) { + console.error('El email ya existe. Intente con otro.') + return + } + + db.data.push({ id, name, email, role }) + db.saveData() +} + +function getUser() { + const db = new LocalDB() + + const id = process.argv[3] + + if (!id) { + console.error('Faltan argumentos. Uso: node 4001_juan-orozco_v1.js GET ') + return + } + + const user = db.data.find(user => user.id === id) + + if (!user) { + console.error('Usuario no encontrado.') + return + } + + const { id: uid, name, email, role } = user + + console.log(`\nID\t\tNombre\t\tEmail\t\t\tRol`) + console.log(`${uid}\t${name}\t${email}\t${role}`) +} + +function deleteUser() { + const db = new LocalDB() + + const id = process.argv[3] + + if (!id) { + console.error('Faltan argumentos. Uso: node 4001_juan-orozco_v1.js DEL ') + return + } + + const userIndex = db.data.findIndex(user => user.id === id) + + if (userIndex === -1) { + console.error('Usuario no encontrado.') + return + } + + db.data = db.data.filter(user => user.id !== id) + db.saveData() +} + +function listUsers() { + const db = new LocalDB() + + if (db.data.length === 0) { + console.log('No hay usuarios registrados.') + return + } + + console.log(`\nID\t\tNombre\t\tEmail\t\t\tRol`) + db.data.forEach(({ id, name, email, role }) => { + console.log(`${id}\t${name}\t${email}\t${role}`) + }) +} + +const commands = { + add: addUser, + get: getUser, + del: deleteUser, + list: listUsers +} + +function main() { + const args = process.argv.slice(2); + const command = args[0]; + + if (commands[command.toLowerCase()]) { + commands[command]() + } else { + console.error('Comando no reconocido. Use ADD, GET, DEL o LIST.') + } +} + +main() \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..d15a1e3 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "codexpress-nivelintegraciones", + "version": "1.0.0", + "description": "CodExpress — Nivel Sistemas e Integraciones", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/JDCodeWork/CodExpress-NivelIntegraciones.git" + }, + "keywords": [], + "author": "Juan Orozco", + "license": "ISC", + "type": "module", + "bugs": { + "url": "https://github.com/JDCodeWork/CodExpress-NivelIntegraciones/issues" + }, + "homepage": "https://github.com/JDCodeWork/CodExpress-NivelIntegraciones#readme" +} From 7552f155bfe8da5451e5f45146bf7b5043cc75dc Mon Sep 17 00:00:00 2001 From: Juan Orozco | JDCodeWork Date: Fri, 3 Oct 2025 15:49:52 -0500 Subject: [PATCH 2/5] Reto 4002 completado --- 4002_juan-orozco_v1.js | 114 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 4002_juan-orozco_v1.js diff --git a/4002_juan-orozco_v1.js b/4002_juan-orozco_v1.js new file mode 100644 index 0000000..60aa3e4 --- /dev/null +++ b/4002_juan-orozco_v1.js @@ -0,0 +1,114 @@ +import fs from 'fs' +import path from 'path' + +function generateSeed() { + const rates = { + USD: { + EUR: 0.94, + GBP: 0.81, + JPY: 149.52, + MXN: 18.23, + COP: 4185.50 + }, + EUR: { + USD: 1.06, + GBP: 0.86, + JPY: 159.12, + MXN: 19.40, + COP: 4450.75 + }, + GBP: { + USD: 1.23, + EUR: 1.16, + JPY: 185.02, + MXN: 22.53, + COP: 5170.40 + }, + JPY: { + USD: 0.0067, + EUR: 0.0063, + GBP: 0.0054, + MXN: 0.12, + COP: 28.00 + }, + MXN: { + USD: 0.055, + EUR: 0.052, + GBP: 0.044, + JPY: 8.33, + COP: 229.50 + }, + COP: { + USD: 0.00024, + EUR: 0.00022, + GBP: 0.00019, + JPY: 0.036, + MXN: 0.0044 + } + }; + + + const dirName = path.dirname(new URL(import.meta.url).pathname) + const filePath = path.join(dirName, 'rates.json') + fs.writeFileSync(filePath, JSON.stringify(rates)) + + console.log('\nArchivo rates.json generado con éxito.') +} + +async function fetchWithBackoffJitter(url, retries = 5, delay = 500) { + for (let i = 0; i < retries; i++) { + try { + const response = await fetch(url) + + if (!response.ok) throw new Error(response.status) + + return await response.json() + } catch (error) { + if (i === retries - 1) throw error + + if (error.status != 429 || error.status != 503) { + throw error + } + + console.log(`\nError al obtener datos: ${error.message}`) + console.log(`Reintento ${i + 1} en ${delay}ms...`) + + const jitter = Math.random() * 100 + await new Promise(res => setTimeout(res, delay + jitter)) + + delay *= 2 + } + } +} + +async function main() { + if (process.argv[2] == "--seed") { + generateSeed() + return + } + + const [amount, fromCurrency, toCurrency, fileName] = process.argv.slice(2) + + if (!amount || !fromCurrency || !toCurrency || !fileName) { + console.error('Faltan argumentos. Uso: node 4002_juan-orozco_v1.js ') + return + } + + try { + const data = await fetchWithBackoffJitter(`http://localhost:8000/${fileName}`) + + if (!data[fromCurrency] || !data[fromCurrency][toCurrency]) { + console.error(`No se encontró la tasa de cambio de ${fromCurrency} a ${toCurrency}.`) + return + } + + const rate = data[fromCurrency][toCurrency] + const convertedAmount = (parseFloat(amount) * rate).toFixed(2) + + console.log(`\n${amount} ${fromCurrency} → ${toCurrency} = ${convertedAmount}`) + } catch (error) { + console.error(`Error al procesar la conversión: ${error.message}`) + } +} + +main() \ No newline at end of file From 2f200952dab7f2e2028745e48216bcbbdbb359c7 Mon Sep 17 00:00:00 2001 From: Juan Orozco | JDCodeWork Date: Fri, 3 Oct 2025 15:56:44 -0500 Subject: [PATCH 3/5] Arreglo en los retos 4001 y 4002 --- 4001_juan-orozco_v1.js | 28 ++++++++++------------------ 4002_juan-orozco_v1.js | 5 +---- 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/4001_juan-orozco_v1.js b/4001_juan-orozco_v1.js index 17847f9..40dcfc1 100644 --- a/4001_juan-orozco_v1.js +++ b/4001_juan-orozco_v1.js @@ -37,18 +37,12 @@ function addUser() { } if (db.data.find(user => user.id === id)) { - console.error('El identificador ya existe. Intente con otro.') - return - } - - // Se pone esta restricción para evitar nombres muy largos en la tabla - if (name.length >= 15) { - console.error('El nombre no debe exceder los 15 caracteres.') + console.error('ERROR:DUP_ID') return } if (db.data.find(user => user.email === email)) { - console.error('El email ya existe. Intente con otro.') + console.error('ERROR:DUP_EMAIL') return } @@ -69,7 +63,7 @@ function getUser() { const user = db.data.find(user => user.id === id) if (!user) { - console.error('Usuario no encontrado.') + console.error('ERROR:USUARIO_NO_ENCONTRADO') return } @@ -92,7 +86,7 @@ function deleteUser() { const userIndex = db.data.findIndex(user => user.id === id) if (userIndex === -1) { - console.error('Usuario no encontrado.') + console.error('ERROR:USUARIO_NO_ENCONTRADO') return } @@ -103,15 +97,13 @@ function deleteUser() { function listUsers() { const db = new LocalDB() - if (db.data.length === 0) { - console.log('No hay usuarios registrados.') - return - } - console.log(`\nID\t\tNombre\t\tEmail\t\t\tRol`) - db.data.forEach(({ id, name, email, role }) => { - console.log(`${id}\t${name}\t${email}\t${role}`) - }) + + db.data + .sort((a, b) => a.id - b.id) + .forEach(({ id, name, email, role }) => { + console.log(`${id}\t${name}\t${email}\t${role}`) + }) } const commands = { diff --git a/4002_juan-orozco_v1.js b/4002_juan-orozco_v1.js index 60aa3e4..ddf3975 100644 --- a/4002_juan-orozco_v1.js +++ b/4002_juan-orozco_v1.js @@ -51,8 +51,6 @@ function generateSeed() { const dirName = path.dirname(new URL(import.meta.url).pathname) const filePath = path.join(dirName, 'rates.json') fs.writeFileSync(filePath, JSON.stringify(rates)) - - console.log('\nArchivo rates.json generado con éxito.') } async function fetchWithBackoffJitter(url, retries = 5, delay = 500) { @@ -70,7 +68,6 @@ async function fetchWithBackoffJitter(url, retries = 5, delay = 500) { throw error } - console.log(`\nError al obtener datos: ${error.message}`) console.log(`Reintento ${i + 1} en ${delay}ms...`) const jitter = Math.random() * 100 @@ -107,7 +104,7 @@ async function main() { console.log(`\n${amount} ${fromCurrency} → ${toCurrency} = ${convertedAmount}`) } catch (error) { - console.error(`Error al procesar la conversión: ${error.message}`) + console.error(`Error ${error.message}`) } } From ce067af9312852d0da3b3d21a2b3ada4d22fb5a6 Mon Sep 17 00:00:00 2001 From: Juan Orozco | JDCodeWork Date: Fri, 3 Oct 2025 16:11:17 -0500 Subject: [PATCH 4/5] Comentarios en los retos 4001 y 4002 --- 4001_juan-orozco_v1.js | 30 +++++++++++++++++++++++++----- 4002_juan-orozco_v1.js | 29 +++++++++++++++++++++++++++-- 2 files changed, 52 insertions(+), 7 deletions(-) diff --git a/4001_juan-orozco_v1.js b/4001_juan-orozco_v1.js index 40dcfc1..c7779da 100644 --- a/4001_juan-orozco_v1.js +++ b/4001_juan-orozco_v1.js @@ -69,8 +69,8 @@ function getUser() { const { id: uid, name, email, role } = user - console.log(`\nID\t\tNombre\t\tEmail\t\t\tRol`) - console.log(`${uid}\t${name}\t${email}\t${role}`) + console.log(`\nID\t| Nombre\t| Email\t\t| Rol`) + console.log(`${uid}\t| ${name}\t| ${email}\t| ${role}`) } function deleteUser() { @@ -115,13 +115,33 @@ const commands = { function main() { const args = process.argv.slice(2); - const command = args[0]; + const command = args[0].toLocaleLowerCase(); - if (commands[command.toLowerCase()]) { + if (commands[command]) { commands[command]() } else { console.error('Comando no reconocido. Use ADD, GET, DEL o LIST.') } } -main() \ No newline at end of file +main() + + +/* + +# Pruebas + 1) Agregar usuario: + terminal: node 4001_juan-orozco_v1.js ADD 1,Juan,juan@example.com,admin + salida: + - Ninguna salida en terminal + - users.json se actualiza con el nuevo usuario + + 2) Obtener usuario existente: + terminal: node 4001_juan-orozco_v1.js GET 1 + salida: + ID | Nombre | Email | Rol + 1 | Juan | juan@example.com | admin + 3) Obtener usuario no existente: + terminal: node 4001_juan-orozco_v1.js GET 99 + salida: ERROR:USUARIO_NO_ENCONTRADO +*/ \ No newline at end of file diff --git a/4002_juan-orozco_v1.js b/4002_juan-orozco_v1.js index ddf3975..6bcdbc7 100644 --- a/4002_juan-orozco_v1.js +++ b/4002_juan-orozco_v1.js @@ -104,8 +104,33 @@ async function main() { console.log(`\n${amount} ${fromCurrency} → ${toCurrency} = ${convertedAmount}`) } catch (error) { - console.error(`Error ${error.message}`) + if (error.status === 404) { + console.error(`ERROR:NO_SE_ENCONTRO_EL_ARCHIVO`) + } else { + console.error(`ERROR: ${error.message}`) + } } } -main() \ No newline at end of file +main() + + +/* +# Pruebas + + 1) Generar archivo de tasas: + terminal: node 4002_juan-orozco_v1.js --seed + salida: + - Ninguna salida en terminal + - rates.json se crea con las tasas de cambio + + 2) Convertir moneda (archivo existe y tasa existe): + terminal: node 4002_juan-orozco_v1.js 100 USD EUR rates.json + salida esperada en terminal: + 100 USD → EUR = 94.00 + + 3) Convertir moneda (archivo no existe): + terminal: node 4002_juan-orozco_v1.js 100 USD EUR nonexistent.json + salida esperada en terminal: + ERROR:NO_SE_ENCONTRO_EL_ARCHIVO +*/ \ No newline at end of file From 9d4c3b15ab384d3d54f9a9b5aa086520510b42e8 Mon Sep 17 00:00:00 2001 From: Juan Orozco | JDCodeWork Date: Fri, 3 Oct 2025 17:03:56 -0500 Subject: [PATCH 5/5] Reto 4005 completado --- 4005_juan-orozco_v1.js | 202 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 4005_juan-orozco_v1.js diff --git a/4005_juan-orozco_v1.js b/4005_juan-orozco_v1.js new file mode 100644 index 0000000..4295eb9 --- /dev/null +++ b/4005_juan-orozco_v1.js @@ -0,0 +1,202 @@ +import fs from 'fs'; +import path from 'path'; + +function parseCSV(csvText) { + const lines = csvText.trim().split("\n"); + + const headers = lines[0].split(",").map(h => h.trim()); + + const data = lines.slice(1).map(line => { + const values = line.split(",").map(v => v.trim()); + const row = {}; + + headers.forEach((header, i) => { + row[header] = values[i] || null; + }); + + return row; + }); + + return data; +} + +function readFiles(csvFileName, jsonSchemaFileName) { + const dirName = path.dirname(new URL(import.meta.url).pathname) + const csvPath = path.join(dirName, csvFileName) + const jsonSchemaPath = path.join(dirName, jsonSchemaFileName) + + const csvText = fs.readFileSync(csvPath, 'utf-8'); + const jsonSchemaText = fs.readFileSync(jsonSchemaPath, 'utf-8'); + + return { csvText, jsonSchemaText } +} + +function parseToSchema(data, schema) { + const jsonSchema = JSON.parse(schema) + const errors = [] + + data.forEach((row, rowIndex) => { + Object.keys(jsonSchema).forEach(column => { + const rules = jsonSchema[column] + const value = row[column] + + if (rules.required && value === null) { + errors.push(`ERR: Fila ${rowIndex + 1} -> La columna "${column}" es obligatoria.`) + return + } + + if (rules.type === 'string') { + if (typeof value !== 'string' && rules.required) { + errors.push(`ERR: Fila ${rowIndex + 1} -> La columna "${column}" debe ser una cadena.`) + return + } + + if (rules.min !== undefined && value.length < rules.min) { + errors.push(`ERR: Fila ${rowIndex + 1} -> La columna "${column}" debe tener al menos ${rules.min} caracteres.`) + } + + if (rules.max !== undefined && value.length > rules.max) { + errors.push(`ERR: Fila ${rowIndex + 1} -> La columna "${column}" debe tener como máximo ${rules.max} caracteres.`) + } + + } else if (rules.type === 'integer') { + const intValue = parseInt(value, 10) + + if (isNaN(intValue) && rules.required) { + errors.push(`ERR: Fila ${rowIndex + 1} -> La columna "${column}" debe ser un entero.`) + return + } + if (rules.min !== undefined && intValue < rules.min) { + errors.push(`ERR: Fila ${rowIndex + 1} -> La columna "${column}" debe ser al menos ${rules.min}.`) + } + if (rules.max !== undefined && intValue > rules.max) { + errors.push(`ERR: Fila ${rowIndex + 1} -> La columna "${column}" debe ser como máximo ${rules.max}.`) + } + } else { + errors.push(`ERR: Fila ${rowIndex + 1} -> Tipo desconocido para la columna "${column}".`) + } + }) + }) + + if (errors.length === 0) { + const parsedData = data.map(row => { + Object.keys(row).forEach(key => { + if (row[key] === null) { + delete row[key] + } + }) + + return row + }) + + + return { data: parsedData } + } + + return { errors } +} + +function writeToJson(data, outputFileName) { + const dirName = path.dirname(new URL(import.meta.url).pathname) + const outputFilePath = path.join(dirName, outputFileName) + fs.writeFileSync(outputFilePath, JSON.stringify(data), 'utf-8'); +} + +function main() { + const [csvFileName, jsonSchemaFileName] = process.argv.slice(2); + + if (!csvFileName || !jsonSchemaFileName) { + console.error('Faltan argumentos. Uso: node 4005_juan-orozco_v1.js '); + return; + } + + const { csvText, jsonSchemaText } = readFiles(csvFileName, jsonSchemaFileName); + + const data = parseCSV(csvText); + const { errors, data: parsedData } = parseToSchema(data, jsonSchemaText); + + if (errors) { + errors.forEach(err => console.error(err)); + return; + } + + writeToJson(parsedData, csvFileName.replace('.csv', '.json')); +} + +main() + +/* Esquema JSON + +{ + : { + "required": , + "type": <"string" | "integer">, + "min": , // Propiedad opcional, para strings es longitud mínima y para enteros es valor mínimo + "max": // Propiedad opcional, para strings es longitud máxima y para enteros es valor máximo + }, + ... +} + + Ejemplo: +{ + "id": { "required": true, "type": "string", "min": 1, "max": 10 }, + "name": { "required": true, "type": "string", "min": 1, "max": 100 }, + "email": { "required": true, "type": "string", "min": 5, "max": 100 }, + "age": { "required": false, "type": "integer", "min": 0, "max": 150 } +} +*/ + +/* +# Pruebas + 1) Transformar el archivo `data.csv` al esquema definido en `data.schema.json` sin ningún error. + - data.csv: + id,name,email,age + 1,John Doe,john@example.com,30 + 2,Jane Smith,jane@example.com,25 + 3,Bob Johnson,bob@example.com, + - data.schema.json: + { + "id": { "required": true, "type": "string", "min": 1, "max": 10 }, + "name": { "required": true, "type": "string", "min": 1, "max": 100 }, + "email": { "required": true, "type": "string", "min": 5, "max": 100 }, + "age": { "required": false, "type": "integer", "min": 0, "max": 150 } + } + - Resultado esperado: + [{"id":"1","name":"John Doe","email":"john@example.com","age":"30"},{"id":"2","name":"Jane Smith","email":"jane@example.com","age":"25"},{"id":"3","name":"Bob Johnson","email":"bob@example.com"}] + - Comando: node 4005_juan-orozco_v1.js data.csv data.schema.json + + 2) Probar con un archivo CSV que tenga errores de validación según el esquema JSON. + - data_invalid.csv: + id,name,email,age + 1,John Doe,john@example.com,30 + 2,Jane Smith,jane@example.com,25 + 3,Bob Johnson,bob@example.com,200 + 4,,noemail,20 + 5,Alice Wonderland,alice@example.com,-5 + - data.schema.json: + { + "id": { "required": true, "type": "string", "min": 1, "max": 10 }, + "name": { "required": true, "type": "string", "min": 1, "max": 100 }, + "email": { "required": true, "type": "string", "min": 10, "max": 100 }, + "age": { "required": false, "type": "integer", "min": 0, "max": 150 } + } + - Resultado esperado: + ERR: Fila 3 -> La columna "age" debe ser como máximo 150. + ERR: Fila 4 -> La columna "name" es obligatoria. + ERR: Fila 4 -> La columna "email" debe tener al menos 10 caracteres. + ERR: Fila 5 -> La columna "age" debe ser al menos 0. + - Comando: node 4005_juan-orozco_v1.js data_invalid.csv data.schema.json + + 3) Probar con un archivo CSV vacío. + - empty.csv: (archivo vacío) + - data.schema.json: + { + "id": { "required": true, "type": "string", "min": 1, "max": 10 }, + "name": { "required": true, "type": "string", "min": 1, "max": 100 }, + "email": { "required": true, "type": "string", "min": 5, "max": 100 }, + "age": { "required": false, "type": "integer", "min": 0, "max": 150 } + } + - Resultado esperado: (archivo JSON vacío) + [] + - Comando: node 4005_juan-orozco_v1.js empty.csv data.schema.json + */ \ No newline at end of file