Respaldo del 19-04-2025
This commit is contained in:
parent
70325519df
commit
73a8c4ff2b
35
.gitignore
vendored
Normal file
35
.gitignore
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
# Ignorar los directorios de dependencias
|
||||
node_modules/
|
||||
|
||||
# Ignorar las carpetas de bases de datos
|
||||
.db/
|
||||
|
||||
# Ignorar archivos de configuración del editor
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
# Ignorar archivos de logs
|
||||
*.log
|
||||
|
||||
# Ignorar archivos de configuración del sistema
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Ignorar archivos de compilación o distribución
|
||||
/dist/
|
||||
/build/
|
||||
|
||||
# Ignorar archivos temporales de tests
|
||||
test/
|
||||
tests/
|
||||
|
||||
# Ignorar archivos de configuración de Docker
|
||||
.dockerignore
|
||||
|
||||
# Ignorar archivos de configuración de Git
|
||||
.gitmodules
|
||||
|
||||
# Ignorar archivos personales o privados (si existen)
|
||||
.env.*
|
||||
*.pem
|
||||
*.key
|
||||
28
Dockerfile.dev
Normal file
28
Dockerfile.dev
Normal file
@ -0,0 +1,28 @@
|
||||
# Dockerfile.dev
|
||||
FROM node:23-slim
|
||||
|
||||
# Definir variables de entorno con valores predeterminados
|
||||
ARG NODE_ENV=development
|
||||
ARG PORT=3000
|
||||
|
||||
# Definir las variables de entorno dentro del contenedor
|
||||
ENV NODE_ENV=${NODE_ENV}
|
||||
ENV PORT=${PORT}
|
||||
|
||||
# Crea directorio de trabajo
|
||||
WORKDIR /app
|
||||
|
||||
# Copia archivos de configuración primero para aprovechar el cache
|
||||
COPY package*.json ./
|
||||
|
||||
# Instala dependencias (incluye devDependencies)
|
||||
RUN npm install
|
||||
|
||||
# Copia el resto de la app
|
||||
COPY . .
|
||||
|
||||
# Expone el puerto
|
||||
EXPOSE ${PORT}
|
||||
|
||||
# Usa nodemon para hot reload si lo tenés
|
||||
CMD ["npx", "nodemon", "src/index.js"]
|
||||
31
Dockerfile.prod
Normal file
31
Dockerfile.prod
Normal file
@ -0,0 +1,31 @@
|
||||
# Dockerfile.prod
|
||||
FROM node:23-slim
|
||||
|
||||
# Definir variables de entorno con valores predeterminados
|
||||
ARG NODE_ENV=production
|
||||
ARG PORT=8080
|
||||
|
||||
# Definir las variables de entorno dentro del contenedor
|
||||
ENV NODE_ENV=${NODE_ENV}
|
||||
ENV PORT=${PORT}
|
||||
|
||||
# Crea directorio de trabajo
|
||||
WORKDIR /app
|
||||
|
||||
# Copia solo archivos necesarios para prod
|
||||
COPY package*.json ./
|
||||
|
||||
# Instala solo dependencias de producción
|
||||
RUN npm install --omit=dev
|
||||
|
||||
# Copia el resto de la app
|
||||
COPY . .
|
||||
|
||||
# Expone el puerto
|
||||
EXPOSE ${PORT}
|
||||
|
||||
# Ejecutar el servidor con nodemon en desarrollo, o con node en producción
|
||||
CMD ["npm", "start"]
|
||||
|
||||
# # Corre la app normalmente
|
||||
# CMD ["node", "src/index.js"]
|
||||
73
db/init/init.sql
Normal file
73
db/init/init.sql
Normal file
@ -0,0 +1,73 @@
|
||||
-- Crear la base de datos solo si no existe
|
||||
CREATE DATABASE IF NOT EXISTS `suitecoffee`;
|
||||
|
||||
USE `suitecoffee`;
|
||||
|
||||
-- Crear tabla de categorías
|
||||
CREATE TABLE IF NOT EXISTS categorias (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
nombre VARCHAR(100) NOT NULL UNIQUE
|
||||
);
|
||||
|
||||
-- Crear tabla de productos
|
||||
CREATE TABLE IF NOT EXISTS productos (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
nombre VARCHAR(100) NOT NULL,
|
||||
precio DECIMAL(10,2) NOT NULL,
|
||||
categoria_id INT,
|
||||
FOREIGN KEY (categoria_id) REFERENCES categorias(id)
|
||||
);
|
||||
|
||||
-- Crear tabla de mesas
|
||||
CREATE TABLE IF NOT EXISTS mesas (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
numero INT NOT NULL UNIQUE
|
||||
);
|
||||
|
||||
-- Crear tabla de comandas con productos en JSON
|
||||
CREATE TABLE IF NOT EXISTS comandas (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
mesa_id INT NOT NULL,
|
||||
productos JSON NOT NULL, -- Array de productos con cantidad y precio
|
||||
fecha DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
total DECIMAL(10,2),
|
||||
FOREIGN KEY (mesa_id) REFERENCES mesas(id)
|
||||
);
|
||||
|
||||
-- Insertar categoría 'Café' en la tabla categorias
|
||||
INSERT INTO categorias (nombre) VALUES ('cafe');
|
||||
|
||||
-- Insertar mesa '1, 2 y 3' en la tabla mesas
|
||||
INSERT INTO mesas (numero)
|
||||
VALUES
|
||||
(1),
|
||||
(2),
|
||||
(3);
|
||||
|
||||
-- Insertar cappuccino en la tabla productos, asociándolo con la categoría 'Café'
|
||||
INSERT INTO productos (nombre, precio, categoria_id)
|
||||
VALUES
|
||||
('Cappuccino', 200.00, (SELECT id FROM categorias WHERE nombre = 'Café')),
|
||||
('Latte', 200.00, (SELECT id FROM categorias WHERE nombre = 'Café')),
|
||||
('Espresso', 120.00, (SELECT id FROM categorias WHERE nombre = 'Café'));
|
||||
('Frappe', 290.00, (SELECT id FROM categorias WHERE nombre = 'Café'));
|
||||
|
||||
-- Insertar una comanda en la tabla comandas para la mesa 1
|
||||
INSERT INTO comandas (mesa_id, productos, total)
|
||||
VALUES
|
||||
(
|
||||
2, -- mesa_id
|
||||
JSON_ARRAY(
|
||||
JSON_OBJECT('producto_id', (SELECT id FROM productos WHERE nombre = 'Expresso'), 'cantidad', 2, 'precio_unitario', 111.00),
|
||||
JSON_OBJECT('producto_id', (SELECT id FROM productos WHERE nombre = 'Latte'), 'cantidad', 1, 'precio_unitario', 666.00)
|
||||
),
|
||||
208457935.00 -- total (2 Cappuccinos * 200 + 1 Latte * 220)
|
||||
),
|
||||
(
|
||||
3, -- mesa_id
|
||||
JSON_ARRAY(
|
||||
JSON_OBJECT('producto_id', (SELECT id FROM productos WHERE nombre = 'Cappuccino'), 'cantidad', 2, 'precio_unitario', 444.00),
|
||||
JSON_OBJECT('producto_id', (SELECT id FROM productos WHERE nombre = 'Frappe'), 'cantidad', 4, 'precio_unitario', 222.00)
|
||||
),
|
||||
93826.00 -- total (2 Cappuccinos * 200 + 1 Latte * 220)
|
||||
);
|
||||
34
docker-compose.dev.yml
Normal file
34
docker-compose.dev.yml
Normal file
@ -0,0 +1,34 @@
|
||||
# docker-compose.dev.yml
|
||||
|
||||
services:
|
||||
suitecoffee-app:
|
||||
container_name: suitecoffee-app
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.dev
|
||||
volumes:
|
||||
- .:/app
|
||||
ports:
|
||||
- "${PORT}:${PORT}" # Usa la variable de entorno PORT
|
||||
environment:
|
||||
- NODE_ENV=development
|
||||
- PORT=${PORT}
|
||||
command: npm run dev # Usa nodemon para desarrollo
|
||||
restart: unless-stopped
|
||||
|
||||
suitecoffee-db:
|
||||
container_name: suitecoffee-db
|
||||
image: mysql:latest
|
||||
env_file:
|
||||
- .env.${NODE_ENV}
|
||||
environment:
|
||||
MYSQL_USER: $DB_USER
|
||||
MYSQL_PASSWORD: $DB_PASS
|
||||
MYSQL_ROOT_PASSWORD: $DB_ROOT_PASSWORD
|
||||
MYSQL_DATABASE: $DB_NAME
|
||||
volumes:
|
||||
- ./db/dev-db:/var/lib/mysql
|
||||
- ./db/init:/docker-entrypoint-initdb.d
|
||||
ports:
|
||||
- "$DB_LOCAL_PORT:$DB_DOCKER_PORT"
|
||||
restart: unless-stopped
|
||||
32
docker-compose.prod.yml
Normal file
32
docker-compose.prod.yml
Normal file
@ -0,0 +1,32 @@
|
||||
# docker-compose.prod.yml
|
||||
|
||||
services:
|
||||
suitecoffee-app:
|
||||
container_name: suitecoffee-app
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.prod
|
||||
ports:
|
||||
- "${PORT}:${PORT}" # Usa la variable de entorno PORT
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- PORT=${PORT}
|
||||
command: npm start # Usa el comando de inicio en producción
|
||||
restart: unless-stopped
|
||||
|
||||
suitecoffee-db:
|
||||
container_name: suitecoffee-db
|
||||
image: mysql:latest
|
||||
env_file:
|
||||
- .env.${NODE_ENV}
|
||||
environment:
|
||||
MYSQL_USER: $DB_USER
|
||||
MYSQL_PASSWORD: $DB_PASS
|
||||
MYSQL_ROOT_PASSWORD: $DB_ROOT_PASSWORD
|
||||
MYSQL_DATABASE: $DB_NAME
|
||||
volumes:
|
||||
- ./db/app-db/mysql_prod:/var/lib/mysql
|
||||
- ./db/init:/docker-entrypoint-initdb.d
|
||||
ports:
|
||||
- "$DB_LOCAL_PORT:$DB_DOCKER_PORT"
|
||||
restart: unless-stopped
|
||||
250
index.js
Normal file
250
index.js
Normal file
@ -0,0 +1,250 @@
|
||||
// index.js
|
||||
const express = require('express');
|
||||
const path = require('path');
|
||||
const dotenv = require('dotenv');
|
||||
const mysql = require('mysql2/promise');
|
||||
const cors = require('cors');
|
||||
|
||||
const app = express();
|
||||
const port = process.env.PORT || 3000;
|
||||
|
||||
// Cargar las variables de entorno dependiendo del entorno
|
||||
const envFile = process.env.NODE_ENV === 'production' ? '.env.production' : '.env.development';
|
||||
dotenv.config({ path: envFile });
|
||||
|
||||
// Configuración de conexión MySQL
|
||||
const dbConfig = {
|
||||
host: process.env.DB_HOST || 'db',
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASS,
|
||||
database: process.env.DB_NAME,
|
||||
port: process.env.DB_DOCKER_PORT || 3306
|
||||
};
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
|
||||
// Servir archivos estáticos de la carpeta 'src'
|
||||
app.use(express.static(path.join(__dirname, 'src')));
|
||||
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// --------------------------------- RENDERIZADO --------------------------------------
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
||||
// Ruta principal
|
||||
app.get('/', (req, res) => {
|
||||
res.sendFile(path.join(__dirname, 'src', 'index.html'));
|
||||
});
|
||||
|
||||
// Ruta para comandas
|
||||
app.get('/comandas', (req, res) => {
|
||||
res.sendFile(path.join(__dirname, 'src', 'pages', 'comandas.html'));
|
||||
});
|
||||
// Ruta para dashboard
|
||||
app.get('/lectura', (req, res) => {
|
||||
res.sendFile(path.join(__dirname, 'src', 'pages', 'lectura.html'));
|
||||
});
|
||||
// Ruta para dashboard
|
||||
app.get('/carga', (req, res) => {
|
||||
res.sendFile(path.join(__dirname, 'src', 'pages', 'carga.html'));
|
||||
});
|
||||
|
||||
// Ruta para obtener las tablas de la base de datos
|
||||
app.get('/tablas', async (req, res) => {
|
||||
let connection;
|
||||
try {
|
||||
connection = await mysql.createConnection(dbConfig);
|
||||
const [rows] = await connection.execute(
|
||||
`SHOW TABLES FROM \`${dbConfig.database}\``
|
||||
);
|
||||
const tablas = rows.map(row => Object.values(row)[0]);
|
||||
res.json({ tablas });
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error al conectar o consultar la base de datos:', error);
|
||||
res.status(500).json({ error: 'Error interno al consultar la base de datos' });
|
||||
|
||||
} finally {
|
||||
if (connection) {
|
||||
await connection.end();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// ----------------------------------- LECTURAS ---------------------------------------
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
// Obtener mesas
|
||||
app.get('/api/obtenerMesas', async (req, res) => {
|
||||
let connection;
|
||||
try {
|
||||
connection = await mysql.createConnection(dbConfig);
|
||||
const [results] = await connection.query('SELECT * FROM mesas');
|
||||
res.json(results);
|
||||
} catch (error) {
|
||||
console.error('Error al obtener mesas:', error);
|
||||
res.status(500).json({ error: 'Error al obtener mesas' });
|
||||
} finally {
|
||||
if (connection) await connection.end();
|
||||
}
|
||||
});
|
||||
|
||||
// Obtener productos
|
||||
app.get('/api/obtenerProductos', async (req, res) => {
|
||||
let connection;
|
||||
try {
|
||||
connection = await mysql.createConnection(dbConfig);
|
||||
const [results] = await connection.query('SELECT * FROM productos');
|
||||
res.json(results);
|
||||
} catch (error) {
|
||||
console.error('Error al obtener productos:', error);
|
||||
res.status(500).json({ error: 'Error al obtener productos' });
|
||||
} finally {
|
||||
if (connection) await connection.end();
|
||||
}
|
||||
});
|
||||
|
||||
// Obtener categorías
|
||||
app.get('/api/obtenerCategorias', async (req, res) => {
|
||||
let connection;
|
||||
try {
|
||||
connection = await mysql.createConnection(dbConfig);
|
||||
const [results] = await connection.query('SELECT * FROM categorias');
|
||||
res.json(results);
|
||||
} catch (error) {
|
||||
console.error('Error al obtener categorías:', error);
|
||||
res.status(500).json({ error: 'Error al obtener categorías' });
|
||||
} finally {
|
||||
if (connection) await connection.end();
|
||||
}
|
||||
});
|
||||
|
||||
// Obtener comandas
|
||||
app.get('/api/obtenerComandas', async (req, res) => {
|
||||
let connection;
|
||||
try {
|
||||
connection = await mysql.createConnection(dbConfig);
|
||||
const [results] = await connection.execute('SELECT * FROM comandas');
|
||||
res.json(results);
|
||||
} catch (error) {
|
||||
console.error('Error al obtener comandas:', error);
|
||||
res.status(500).json({ error: 'Error al obtener comandas' });
|
||||
} finally {
|
||||
if (connection) await connection.end();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// ------------------------------------ CARGAS ----------------------------------------
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
// Cargar nueva mesa
|
||||
app.post('/api/cargarMesas', async (req, res) => {
|
||||
const { numero } = req.body;
|
||||
if (!numero) return res.status(400).json({ error: 'Falta el número de mesa' });
|
||||
|
||||
let connection;
|
||||
try {
|
||||
connection = await mysql.createConnection(dbConfig);
|
||||
await connection.execute('INSERT INTO mesas (numero) VALUES (?)', [numero]);
|
||||
res.status(201).json({ mensaje: 'Mesa cargada correctamente' });
|
||||
} catch (error) {
|
||||
console.error('Error al cargar mesa:', error);
|
||||
res.status(500).json({ error: 'Error al cargar mesa' });
|
||||
} finally {
|
||||
if (connection) await connection.end();
|
||||
}
|
||||
});
|
||||
|
||||
// Cargar nuevo producto
|
||||
app.post('/api/cargarProductos', async (req, res) => {
|
||||
const { nombre, precio, categoria_id } = req.body;
|
||||
if (!nombre || !precio || !categoria_id) {
|
||||
return res.status(400).json({ error: 'Faltan datos para cargar el producto' });
|
||||
}
|
||||
|
||||
let connection;
|
||||
try {
|
||||
connection = await mysql.createConnection(dbConfig);
|
||||
await connection.execute(
|
||||
'INSERT INTO productos (nombre, precio, categoria_id) VALUES (?, ?, ?)',
|
||||
[nombre, precio, categoria_id]
|
||||
);
|
||||
res.status(201).json({ mensaje: 'Producto cargado correctamente' });
|
||||
} catch (error) {
|
||||
console.error('Error al cargar producto:', error);
|
||||
res.status(500).json({ error: 'Error al cargar producto' });
|
||||
} finally {
|
||||
if (connection) await connection.end();
|
||||
}
|
||||
});
|
||||
|
||||
// Cargar nueva categoría
|
||||
app.post('/api/cargarCategorias', async (req, res) => {
|
||||
const { nombre } = req.body;
|
||||
if (!nombre) return res.status(400).json({ error: 'Falta el nombre de la categoría' });
|
||||
|
||||
let connection;
|
||||
try {
|
||||
connection = await mysql.createConnection(dbConfig);
|
||||
await connection.execute('INSERT INTO categorias (nombre) VALUES (?)', [nombre]);
|
||||
res.status(201).json({ mensaje: 'Categoría cargada correctamente' });
|
||||
} catch (error) {
|
||||
console.error('Error al cargar categoría:', error);
|
||||
res.status(500).json({ error: 'Error al cargar categoría' });
|
||||
} finally {
|
||||
if (connection) await connection.end();
|
||||
}
|
||||
});
|
||||
|
||||
// Cargar nueva comanda
|
||||
app.post('/api/cargarComandas', async (req, res) => {
|
||||
const { mesa_id, productos, total } = req.body;
|
||||
if (!mesa_id || !productos || !Array.isArray(productos) || total == null) {
|
||||
return res.status(400).json({ error: 'Datos inválidos para cargar comanda' });
|
||||
}
|
||||
let connection;
|
||||
try {
|
||||
connection = await mysql.createConnection(dbConfig);
|
||||
await connection.execute(
|
||||
'INSERT INTO comandas (mesa_id, productos, total, fecha) VALUES (?, ?, ?, NOW())',
|
||||
[mesa_id, JSON.stringify(productos), total]
|
||||
);
|
||||
res.status(201).json({ mensaje: 'Comanda cargada correctamente' });
|
||||
} catch (error) {
|
||||
console.error('Error al cargar comanda:', error);
|
||||
res.status(500).json({ error: 'Error al cargar comanda' });
|
||||
} finally {
|
||||
if (connection) await connection.end();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// --------------------------------- VERIFICACIONES -----------------------------------
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
||||
async function verificarConexion() {
|
||||
try {
|
||||
const connection = await mysql.createConnection(dbConfig);
|
||||
const [rows] = await connection.execute('SELECT NOW() AS hora');
|
||||
console.log('Conexión con la base de datos fue exitosa.');
|
||||
console.log('Fecha y hora actual de la base de datos:', rows[0].hora);
|
||||
await connection.end();
|
||||
} catch (error) {
|
||||
console.error('Error al conectar con la base de datos al iniciar:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Iniciar servidor
|
||||
app.listen(port, () => {
|
||||
console.log(`Servidor corriendo en http://localhost:${port}`);
|
||||
console.log('Estableciendo conexión...');
|
||||
verificarConexion();
|
||||
});
|
||||
1313
package-lock.json
generated
Normal file
1313
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
23
package.json
Normal file
23
package.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "suitecoffee",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "node index.js",
|
||||
"dev": "nodemon index.js"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"type": "commonjs",
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.5.0",
|
||||
"express": "^5.1.0",
|
||||
"mysql2": "^3.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.1.9"
|
||||
}
|
||||
}
|
||||
40
src/index.html
Normal file
40
src/index.html
Normal file
@ -0,0 +1,40 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Login Bootstrap</title>
|
||||
<!-- Bootstrap CDN -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
</head>
|
||||
<body class="bg-light d-flex justify-content-center align-items-center vh-100">
|
||||
|
||||
<div class="card shadow p-4" style="width: 100%; max-width: 350px;">
|
||||
<h4 class="text-center mb-4">Iniciar Sesión</h4>
|
||||
<form>
|
||||
<div class="mb-3">
|
||||
<label for="usuario" class="form-label">Usuario</label>
|
||||
<input type="text" class="form-control" id="usuario" placeholder="Ingrese su usuario" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="clave" class="form-label">Contraseña</label>
|
||||
<input type="password" class="form-control" id="clave" placeholder="Ingrese su contraseña" required>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="recordarme">
|
||||
<label class="form-check-label" for="recordarme">
|
||||
Recordarme
|
||||
</label>
|
||||
</div>
|
||||
<a href="#" class="small">¿Olvidaste tu contraseña?</a>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary w-100">Entrar</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap JS (opcional) -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
116
src/pages/carga.html
Normal file
116
src/pages/carga.html
Normal file
@ -0,0 +1,116 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Cargar Comanda</title>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Formulario de Carga de Comanda</h2>
|
||||
|
||||
<form id="formComanda">
|
||||
<label for="mesaSelect">Mesa:</label>
|
||||
<select id="mesaSelect" name="mesa_id" required></select>
|
||||
<br><br>
|
||||
|
||||
<label for="productoSelect">Producto:</label>
|
||||
<select id="productosSelect"></select>
|
||||
|
||||
<label for="cantidadInput">Cantidad:</label>
|
||||
<input type="number" id="cantidadInput" min="1" value="1">
|
||||
<button type="button" onclick="agregarProducto()">Agregar</button>
|
||||
|
||||
<ul id="listaProductos"></ul>
|
||||
|
||||
<input type="hidden" name="productos" id="productosJSON">
|
||||
|
||||
<br>
|
||||
<button type="submit">Guardar Comanda</button>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
// Cargar categorías y mesas (productos se maneja distinto por el precio)
|
||||
async function cargarSelect(endpoint, selectId, mostrar) {
|
||||
const res = await fetch(endpoint);
|
||||
const data = await res.json();
|
||||
const select = document.getElementById(selectId);
|
||||
data.forEach(item => {
|
||||
const option = document.createElement('option');
|
||||
option.value = item.id;
|
||||
option.textContent = mostrar(item);
|
||||
select.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
async function cargarProductosConPrecio() {
|
||||
const res = await fetch('/api/obtenerProductos');
|
||||
const productos = await res.json();
|
||||
const select = document.getElementById('productosSelect');
|
||||
productos.forEach(prod => {
|
||||
const option = document.createElement('option');
|
||||
option.value = prod.id;
|
||||
option.textContent = `${prod.nombre} ($${prod.precio})`;
|
||||
option.setAttribute('data-precio', prod.precio);
|
||||
select.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
const listaProductos = [];
|
||||
function agregarProducto() {
|
||||
const select = document.getElementById('productosSelect');
|
||||
const cantidad = parseInt(document.getElementById('cantidadInput').value);
|
||||
const productoId = select.value;
|
||||
const nombre = select.options[select.selectedIndex].textContent;
|
||||
const precioUnitario = parseFloat(select.options[select.selectedIndex].dataset.precio);
|
||||
|
||||
if (!productoId || isNaN(cantidad) || cantidad <= 0) {
|
||||
alert("Producto o cantidad inválida");
|
||||
return;
|
||||
}
|
||||
|
||||
listaProductos.push({
|
||||
producto_id: productoId,
|
||||
cantidad,
|
||||
precio_unitario: precioUnitario
|
||||
});
|
||||
|
||||
// Mostrar en lista visual
|
||||
const li = document.createElement('li');
|
||||
li.textContent = `${nombre} x${cantidad} - $${(precioUnitario * cantidad).toFixed(2)}`;
|
||||
document.getElementById('listaProductos').appendChild(li);
|
||||
|
||||
// Actualizar JSON oculto
|
||||
document.getElementById('productosJSON').value = JSON.stringify(listaProductos);
|
||||
}
|
||||
|
||||
document.getElementById('formComanda').addEventListener('submit', async function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
const mesaId = document.getElementById('mesaSelect').value;
|
||||
const productos = listaProductos;
|
||||
|
||||
if (!mesaId || productos.length === 0) {
|
||||
alert('Selecciona una mesa y al menos un producto');
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await fetch('/api/cargarComandas', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
mesa_id: mesaId,
|
||||
productos: productos
|
||||
})
|
||||
});
|
||||
|
||||
const resultado = await res.json();
|
||||
alert(resultado.mensaje || 'Comanda cargada correctamente');
|
||||
location.reload();
|
||||
});
|
||||
|
||||
window.onload = () => {
|
||||
cargarSelect('/api/obtenerMesas', 'mesaSelect', mesa => `Mesa ${mesa.numero}`);
|
||||
cargarProductosConPrecio();
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
100
src/pages/comandas.html
Normal file
100
src/pages/comandas.html
Normal file
@ -0,0 +1,100 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Comanda - Cafetería</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
</head>
|
||||
<body class="bg-light">
|
||||
|
||||
<div class="container my-4">
|
||||
<div class="card shadow">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h4 class="mb-0">🧾 Comanda de Cafetería</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form>
|
||||
<!-- Datos generales -->
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-12 col-md-6">
|
||||
<label for="mesa" class="form-label">Número de Mesa</label>
|
||||
<select class="form-select" id="mesa" required>
|
||||
<option value="">Seleccionar mesa</option>
|
||||
<option>Mesa 1</option>
|
||||
<option>Mesa 2</option>
|
||||
<option>Mesa 3</option>
|
||||
<option>Mesa 4</option>
|
||||
<option>Mesa 5</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<label for="mozo" class="form-label">Mozo/a</label>
|
||||
<input type="text" class="form-control" id="mozo" placeholder="Nombre del mozo/a" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Productos -->
|
||||
<div class="mb-4">
|
||||
<label class="form-label">Productos</label>
|
||||
|
||||
<!-- Producto 1 -->
|
||||
<div class="row g-2 mb-2">
|
||||
<div class="col-12 col-md-6">
|
||||
<select class="form-select">
|
||||
<option value="">Seleccionar producto</option>
|
||||
<option>Café</option>
|
||||
<option>Café con leche</option>
|
||||
<option>Capuccino</option>
|
||||
<option>Medialuna</option>
|
||||
<option>Jugo natural</option>
|
||||
<option>Tostado</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<input type="number" class="form-control" placeholder="Cant.">
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<input type="text" class="form-control" placeholder="Notas">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Producto 2 -->
|
||||
<div class="row g-2 mb-2">
|
||||
<div class="col-12 col-md-6">
|
||||
<select class="form-select">
|
||||
<option value="">Seleccionar producto</option>
|
||||
<option>Café</option>
|
||||
<option>Café con leche</option>
|
||||
<option>Capuccino</option>
|
||||
<option>Medialuna</option>
|
||||
<option>Jugo natural</option>
|
||||
<option>Tostado</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<input type="number" class="form-control" placeholder="Cant.">
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<input type="text" class="form-control" placeholder="Notas">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Observaciones -->
|
||||
<div class="mb-4">
|
||||
<label for="observaciones" class="form-label">Observaciones</label>
|
||||
<textarea class="form-control" id="observaciones" rows="3" placeholder="Ej: Sin azúcar, entregar cuando esté completo"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="d-grid">
|
||||
<button type="submit" class="btn btn-success">Enviar Comanda</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
43
src/pages/lectura.html
Normal file
43
src/pages/lectura.html
Normal file
@ -0,0 +1,43 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Dashboard</title>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Mesas</h2>
|
||||
<select id="mesasSelect"></select>
|
||||
|
||||
<h2>Productos</h2>
|
||||
<select id="productosSelect"></select>
|
||||
|
||||
<h2>Categorías</h2>
|
||||
<select id="categoriasSelect"></select>
|
||||
|
||||
<h2>Comandas</h2>
|
||||
<select id="comandasSelect"></select>
|
||||
|
||||
<script>
|
||||
async function cargarDatos(endpoint, selectId, mostrar) {
|
||||
const res = await fetch(endpoint); // Usar endpoint relativo
|
||||
const data = await res.json();
|
||||
const select = document.getElementById(selectId);
|
||||
|
||||
data.forEach(item => {
|
||||
const option = document.createElement('option');
|
||||
option.value = item.id;
|
||||
option.textContent = mostrar(item);
|
||||
select.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
// Al cargar la página, cargamos los datos
|
||||
window.onload = () => {
|
||||
cargarDatos('api/obtenerMesas', 'mesasSelect', mesa => `Mesa ${mesa.numero}`);
|
||||
cargarDatos('api/obtenerProductos', 'productosSelect', prod => `${prod.nombre} ($${prod.precio})`);
|
||||
cargarDatos('api/obtenerCategorias', 'categoriasSelect', cat => cat.nombre);
|
||||
cargarDatos('api/obtenerComandas', 'comandasSelect', com => `Comanda ${com.id} - Mesa ${com.mesa_id} - $${com.total}`);
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
0
suitecoffee.py
Normal file
0
suitecoffee.py
Normal file
Loading…
x
Reference in New Issue
Block a user