diff --git a/4001_juan-orozco_v1.js b/4001_juan-orozco_v1.js new file mode 100644 index 0000000..c7779da --- /dev/null +++ b/4001_juan-orozco_v1.js @@ -0,0 +1,147 @@ +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('ERROR:DUP_ID') + return + } + + if (db.data.find(user => user.email === email)) { + console.error('ERROR:DUP_EMAIL') + 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('ERROR:USUARIO_NO_ENCONTRADO') + return + } + + const { id: uid, name, email, role } = user + + console.log(`\nID\t| Nombre\t| Email\t\t| Rol`) + 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('ERROR:USUARIO_NO_ENCONTRADO') + return + } + + db.data = db.data.filter(user => user.id !== id) + db.saveData() +} + +function listUsers() { + const db = new LocalDB() + + console.log(`\nID\t\tNombre\t\tEmail\t\t\tRol`) + + 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 = { + add: addUser, + get: getUser, + del: deleteUser, + list: listUsers +} + +function main() { + const args = process.argv.slice(2); + const command = args[0].toLocaleLowerCase(); + + if (commands[command]) { + commands[command]() + } else { + console.error('Comando no reconocido. Use ADD, GET, DEL o LIST.') + } +} + +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 new file mode 100644 index 0000000..6bcdbc7 --- /dev/null +++ b/4002_juan-orozco_v1.js @@ -0,0 +1,136 @@ +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)) +} + +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(`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) { + if (error.status === 404) { + console.error(`ERROR:NO_SE_ENCONTRO_EL_ARCHIVO`) + } else { + console.error(`ERROR: ${error.message}`) + } + } +} + +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 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 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" +}