// app/src/index.js import chalk from 'chalk'; // Colores! import express from 'express'; import expressLayouts from 'express-ejs-layouts'; import cors from 'cors'; import { Pool } from 'pg'; // Rutas import path from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Variables de Entorno import dotenv, { config } from 'dotenv'; // Obtención de la ruta de la variable de entorno correspondiente a NODE_ENV try { if (process.env.NODE_ENV === 'development') { dotenv.config({ path: path.resolve(__dirname, '../.env.development' )}); console.log(`Activando entorno de ->${chalk.green(` DEVELOPMENT `)}`); } else if (process.env.NODE_ENV === 'stage') { dotenv.config({ path: path.resolve(__dirname, '../.env.test' )}); console.log(`Activando entorno de ->${chalk.yellow(` TESTING `)}`); } else if (process.env.NODE_ENV === 'production') { dotenv.config({ path: path.resolve(__dirname, '../.env.production' )}); console.log(`Activando entorno de ->${chalk.red(` PRODUCTION `)}`); } } catch (error) { console.log("A ocurrido un error al seleccionar el entorno. \nError: " + error); } // Renderiado const app = express(); app.use(cors()); app.use(express.json()); app.use(express.static(path.join(__dirname, 'pages'))); // Configuración de conexión PostgreSQL const dbConfig = { host: process.env.DB_HOST, user: process.env.DB_USER, password: process.env.DB_PASS, database: process.env.DB_NAME, port: process.env.DB_LOCAL_PORT }; const pool = new Pool(dbConfig); async function verificarConexion() { try { const client = await pool.connect(); const res = await client.query('SELECT NOW() AS hora'); console.log(`\nConexión con la base de datos ${chalk.green(process.env.DB_NAME)} fue exitosa.`); console.log('Fecha y hora actual de la base de datos:', res.rows[0].hora); client.release(); // liberar el cliente de nuevo al pool } catch (error) { console.error('Error al conectar con la base de datos al iniciar:', error.message); console.error(`Troubleshooting:\n1. Compruebe que las bases de datos se iniciaron correctamente.\n2. Verifique las credenciales y puertos de acceso a la base de datos.\n3. Si está conectandose a una base de datos externa a localhost, verifique las reglas del firewal de entrada y salida de ambos dispositivos.`); } } // === Servir páginas estáticas === app.get('/roles', (req, res) => res.sendFile(path.join(__dirname, 'pages', 'roles.html'))); app.get('/usuarios', (req, res) => res.sendFile(path.join(__dirname, 'pages', 'usuarios.html'))); app.get('/categorias',(req, res) => res.sendFile(path.join(__dirname, 'pages', 'categorias.html'))); app.get('/productos', (req, res) => res.sendFile(path.join(__dirname, 'pages', 'productos.html'))); // Helper de consulta con acquire/release explícito async function q(text, params) { const client = await pool.connect(); try { return await client.query(text, params); } finally { client.release(); } } // === API Roles === // GET: listar app.get('/api/roles', async (req, res) => { try { const { rows } = await q('SELECT id_rol, nombre FROM roles ORDER BY id_rol ASC'); res.json(rows); } catch (e) { console.error(e); res.status(500).json({ error: 'No se pudo listar roles' }); } }); // POST: crear app.post('/api/roles', async (req, res) => { try { const { nombre } = req.body; if (!nombre || !nombre.trim()) return res.status(400).json({ error: 'Nombre requerido' }); const { rows } = await q( 'INSERT INTO roles (nombre) VALUES ($1) RETURNING id_rol, nombre', [nombre.trim()] ); res.status(201).json(rows[0]); } catch (e) { console.error(e); // Manejo de único/duplicado if (e.code === '23505') return res.status(409).json({ error: 'El rol ya existe' }); res.status(500).json({ error: 'No se pudo crear el rol' }); } }); // === API Usuarios === // GET: listar app.get('/api/usuarios', async (req, res) => { try { const { rows } = await q(` SELECT id_usuario, documento, img_perfil, nombre, apellido, correo, telefono, fec_nacimiento, activo FROM usuarios ORDER BY id_usuario ASC `); res.json(rows); } catch (e) { console.error(e); res.status(500).json({ error: 'No se pudo listar usuarios' }); } }); // POST: crear app.post('/api/usuarios', async (req, res) => { try { const { documento, nombre, apellido, correo, telefono, fec_nacimiento } = req.body; if (!nombre || !apellido) return res.status(400).json({ error: 'Nombre y apellido requeridos' }); const { rows } = await q(` INSERT INTO usuarios (documento, nombre, apellido, correo, telefono, fec_nacimiento) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id_usuario, documento, nombre, apellido, correo, telefono, fec_nacimiento, activo `, [ documento || null, nombre.trim(), apellido.trim(), correo || null, telefono || null, fec_nacimiento || null ]); res.status(201).json(rows[0]); } catch (e) { console.error(e); if (e.code === '23505') return res.status(409).json({ error: 'Documento/Correo/Teléfono ya existe' }); res.status(500).json({ error: 'No se pudo crear el usuario' }); } }); // === API Categorías === // GET: listar app.get('/api/categorias', async (req, res) => { try { const { rows } = await q('SELECT id_categoria, nombre, visible FROM categorias ORDER BY id_categoria ASC'); res.json(rows); } catch (e) { console.error(e); res.status(500).json({ error: 'No se pudo listar categorías' }); } }); // POST: crear app.post('/api/categorias', async (req, res) => { try { const { nombre, visible } = req.body; if (!nombre || !nombre.trim()) return res.status(400).json({ error: 'Nombre requerido' }); const vis = (typeof visible === 'boolean') ? visible : true; const { rows } = await q(` INSERT INTO categorias (nombre, visible) VALUES ($1, $2) RETURNING id_categoria, nombre, visible `, [nombre.trim(), vis]); res.status(201).json(rows[0]); } catch (e) { console.error(e); if (e.code === '23505') return res.status(409).json({ error: 'La categoría ya existe' }); res.status(500).json({ error: 'No se pudo crear la categoría' }); } }); // === API Productos === // GET: listar app.get('/api/productos', async (req, res) => { try { const { rows } = await q(` SELECT id_producto, nombre, img_producto, precio, activo, id_categoria FROM productos ORDER BY id_producto ASC `); res.json(rows); } catch (e) { console.error(e); res.status(500).json({ error: 'No se pudo listar productos' }); } }); // POST: crear app.post('/api/productos', async (req, res) => { try { let { nombre, id_categoria, precio } = req.body; if (!nombre || !nombre.trim()) return res.status(400).json({ error: 'Nombre requerido' }); id_categoria = parseInt(id_categoria, 10); precio = parseFloat(precio); if (!Number.isInteger(id_categoria)) return res.status(400).json({ error: 'id_categoria inválido' }); if (!(precio >= 0)) return res.status(400).json({ error: 'precio inválido' }); const { rows } = await q(` INSERT INTO productos (nombre, id_categoria, precio) VALUES ($1, $2, $3) RETURNING id_producto, nombre, precio, activo, id_categoria `, [nombre.trim(), id_categoria, precio]); res.status(201).json(rows[0]); } catch (e) { console.error(e); // FK categories / checks if (e.code === '23503') return res.status(400).json({ error: 'La categoría no existe' }); res.status(500).json({ error: 'No se pudo crear el producto' }); } }); // Colores personalizados let primaryColor = chalk.hex('#'+`${process.env.COL_PRI}`); let secondaryColor = chalk.hex('#'+`${process.env.COL_SEC}`); // let backgroundColor = chalk.hex('#'+`${process.env.COL_BG}`); app.use(expressLayouts); // Iniciar servidor app.listen( process.env.PORT, () => { console.log(`Servidor de ${chalk.red('aplicación')} de ${secondaryColor('SuiteCoffee')} corriendo en ${chalk.yellow(`http://localhost:${process.env.PORT}\n`)}` ); console.log(chalk.grey(`Comprobando accesibilidad a la db ${chalk.green(process.env.DB_NAME)} del host ${chalk.white(`${process.env.DB_HOST}`)} ...`)); verificarConexion(); }); app.get("/health", async (req, res) => { // Podés chequear DB aquí. 200 = healthy; 503 = not ready. res.status(200).json({ status: "ok" }); });