TigreHabibi/sql-empire-builder
0
1// Game State2let gameState = {3 productos: [4 { id: 1, nombre: 'PlayStation 5', precio: 500, stock: 10 },5 { id: 2, nombre: 'Xbox Series X', precio: 450, stock: 8 },6 { id: 3, nombre: 'Nintendo Switch', precio: 300, stock: 15 }7 ],8 clientes: [9 { id: 1, nombre: 'Ana García', email: 'ana@email.com', pedidos: [1] },10 { id: 2, nombre: 'Carlos López', email: 'carlos@email.com', pedidos: [2] },11 { id: 3, nombre: 'María Rodríguez', email: 'maria@email.com', pedidos: [] }12 ],13 pedidos: [14 { id: 1, cliente_id: 1, producto_id: 1, cantidad: 1, fecha: '2024-01-15' },15 { id: 2, cliente_id: 2, producto_id: 2, cantidad: 1, fecha: '2024-01-16' }16 ],17 eventos: [],18 dinero: 10000,19 reputacion: 10020};21 22// Initialize game23function initGame() {24 renderTables();25 generateRandomEvent();26 startEventGenerator();27}28 29// Render database tables30function renderTables() {31 // Render productos table32 const productosTable = document.getElementById('productos-table');33 productosTable.innerHTML = gameState.productos.map(producto => `34 <tr class="border-b border-gray-700">35 <td class="p-2">${producto.id}</td>36 <td class="p-2">${producto.nombre}</td>37 <td class="p-2">$${producto.precio}</td>38 <td class="p-2 ${producto.stock < 5 ? 'text-red-400' : 'text-green-400'}">${producto.stock}</td>39 </tr>40 `).join('');41 42 // Render clientes table43 const clientesTable = document.getElementById('clientes-table');44 clientesTable.innerHTML = gameState.clientes.map(cliente => `45 <tr class="border-b border-gray-700">46 <td class="p-2">${cliente.id}</td>47 <td class="p-2">${cliente.nombre}</td>48 <td class="p-2">${cliente.email}</td>49 <td class="p-2">${cliente.pedidos.length}</td>50 </tr>51 `).join('');52 53 // Render events54 renderEvents();55}56 57// Render events panel58function renderEvents() {59 const eventsContainer = document.getElementById('events-container');60 eventsContainer.innerHTML = gameState.eventos.map(evento => `61 <div class="event-item p-3 bg-gray-700 rounded border-l-4 ${getStatusClass(evento.tipo)}">62 <div class="flex items-center justify-between">63 <span class="font-semibold">${evento.titulo}</span>64 <span class="text-xs text-gray-400">${evento.fecha}</span>65 </div>66 <p class="text-sm text-gray-300 mt-1">${evento.descripcion}</p>67 </div>68 `).join('');69}70 71// Execute SQL command72function executeSQL() {73 const sqlInput = document.getElementById('sql-input');74 const sqlResult = document.getElementById('sql-result');75 const command = sqlInput.value.trim();76 77 if (!command) {78 showResult('Por favor, escribe un comando SQL válido.', 'error');79 return;80 }81 82 try {83 const result = processSQLCommand(command);84 showResult(result.message, result.type);85 renderTables();86 87 // Generate new event after successful command88 if (result.type === 'success') {89 setTimeout(generateRandomEvent, 1000);90 }91 } catch (error) {92 showResult(`Error: ${error.message}`, 'error');93 }94 95 sqlInput.value = '';96}97// Process SQL commands98function processSQLCommand(command) {99 const upperCommand = command.toUpperCase();100 101 // INSERT command102 if (upperCommand.startsWith('INSERT INTO PRODUCTOS')) {103 return handleInsertProducto(command);104 }105 // UPDATE command106 else if (upperCommand.startsWith('UPDATE PRODUCTOS')) {107 return handleUpdateProducto(command);108 }109 // DELETE command110 else if (upperCommand.startsWith('DELETE FROM CLIENTES')) {111 return handleDeleteCliente(command);112 }113 // SELECT command (basic support)114 else if (upperCommand.startsWith('SELECT')) {115 return handleSelectCommand(command);116 }117 else {118 throw new Error('Comando SQL no reconocido. Usa INSERT, UPDATE, DELETE o SELECT.');119 }120}121// Handle INSERT INTO Productos122function handleInsertProducto(command) {123 const match = command.match(/INSERT INTO Productos\s*\(([^)]+)\)\s*VALUES\s*\(([^)]+)\)/i);124 if (!match) {125 throw new Error('Formato INSERT incorrecto. Ejemplo: INSERT INTO Productos (nombre, precio, stock) VALUES (\"Producto\", 100, 50);');126 }127 128 const columns = match[1].split(',').map(col => col.trim());129 const values = match[2].split(',').map(val => {130 // Remove quotes and trim131 let cleaned = val.trim().replace(/^['"](.*)['"]$/, '$1');132 // Handle numeric values133 if (!isNaN(cleaned)) {134 return parseInt(cleaned);135 }136 return cleaned;137 });138 139 // Validate required columns140 const requiredColumns = ['nombre', 'precio', 'stock'];141 for (const col of requiredColumns) {142 if (!columns.includes(col)) {143 throw new Error(`Falta la columna requerida: ${col}`);144 }145 }146 147 const nuevoProducto = {148 id: Math.max(0, ...gameState.productos.map(p => p.id)) + 1,149 nombre: values[columns.indexOf('nombre')],150 precio: values[columns.indexOf('precio')],151 stock: values[columns.indexOf('stock')]152 };153 154 // Validate numeric values155 if (isNaN(nuevoProducto.precio) || isNaN(nuevoProducto.stock)) {156 throw new Error('Precio y stock deben ser números válidos');157 }158 159 if (nuevoProducto.precio <= 0 || nuevoProducto.stock < 0) {160 throw new Error('Precio debe ser mayor a 0 y stock no puede ser negativo');161 }162 163 gameState.productos.push(nuevoProducto);164 165 addEvent('Nuevo Producto', `Se ha añadido "${nuevoProducto.nombre}" al catálogo.`, 'success');166 167 return {168 message: `✅ Producto añadido exitosamente. ID: ${nuevoProducto.id}`,169 type: 'success'170 };171}172// Handle UPDATE Productos173function handleUpdateProducto(command) {174 const match = command.match(/UPDATE Productos SET (.+) WHERE (.+)/i);175 if (!match) {176 throw new Error('Formato UPDATE incorrecto. Ejemplo: UPDATE Productos SET stock = 5 WHERE id = 1;');177 }178 179 const setClause = match[1];180 const whereClause = match[2];181 182 // Simple WHERE id = X implementation183 const idMatch = whereClause.match(/id\s*=\s*(\d+)/i);184 if (!idMatch) {185 throw new Error('WHERE clause debe incluir id del producto. Ejemplo: WHERE id = 1');186 }187 188 const productoId = parseInt(idMatch[1]);189 const producto = gameState.productos.find(p => p.id === productoId);190 191 if (!producto) {192 throw new Error(`Producto con ID ${productoId} no encontrado.`);193 }194 195 let updatedFields = [];196 197 // Update fields198 if (setClause.includes('stock')) {199 const stockMatch = setClause.match(/stock\s*=\s*(\d+)/i);200 if (stockMatch) {201 const nuevoStock = parseInt(stockMatch[1]);202 if (isNaN(nuevoStock)) throw new Error('El stock debe ser un número válido.');203 if (nuevoStock < 0) throw new Error('El stock no puede ser negativo.');204 producto.stock = nuevoStock;205 updatedFields.push('stock');206 }207 }208 209 if (setClause.includes('precio')) {210 const precioMatch = setClause.match(/precio\s*=\s*(\d+)/i);211 if (precioMatch) {212 const nuevoPrecio = parseInt(precioMatch[1]);213 if (isNaN(nuevoPrecio)) throw new Error('El precio debe ser un número válido.');214 if (nuevoPrecio <= 0) throw new Error('El precio debe ser mayor a 0.');215 producto.precio = nuevoPrecio;216 updatedFields.push('precio');217 }218 }219 220 if (setClause.includes('nombre')) {221 const nombreMatch = setClause.match(/nombre\s*=\s*'([^']+)'/i) || setClause.match(/nombre\s*=\s*"([^"]+)"/i);222 if (nombreMatch) {223 producto.nombre = nombreMatch[1];224 updatedFields.push('nombre');225 }226 }227 228 if (updatedFields.length === 0) {229 throw new Error('No se encontraron campos válidos para actualizar. Usa stock, precio o nombre.');230 }231 232 addEvent('Inventario Actualizado', `Se ha actualizado el producto "${producto.nombre}". Campos: ${updatedFields.join(', ')}`, 'warning');233 234 return {235 message: `✅ Producto actualizado exitosamente. Campos modificados: ${updatedFields.join(', ')}`,236 type: 'success'237 };238}239// Handle DELETE FROM Clientes240function handleDeleteCliente(command) {241 const match = command.match(/DELETE FROM Clientes WHERE id\s*=\s*(\d+)/i);242 if (!match) {243 throw new Error('Formato DELETE incorrecto. Ejemplo: DELETE FROM Clientes WHERE id = 1;');244 }245 246 const clienteId = parseInt(match[1]);247 if (isNaN(clienteId)) {248 throw new Error('El ID del cliente debe ser un número válido.');249 }250 251 const cliente = gameState.clientes.find(c => c.id === clienteId);252 253 if (!cliente) {254 throw new Error(`Cliente con ID ${clienteId} no encontrado.`);255 }256 257 // Check for foreign key constraint (pedidos activos)258 const tienePedidos = gameState.pedidos.some(p => p.cliente_id === clienteId);259 if (tienePedidos) {260 addEvent('Error de Integridad', `No se puede eliminar al cliente "${cliente.nombre}" porque tiene pedidos activos.`, 'error');261 throw new Error('ERROR: Violación de integridad referencial. El cliente tiene pedidos asociados.');262 }263 264 gameState.clientes = gameState.clientes.filter(c => c.id !== clienteId);265 addEvent('Cliente Eliminado', `Se ha eliminado al cliente "${cliente.nombre}".`, 'warning');266 267 return {268 message: `✅ Cliente eliminado exitosamente.`,269 type: 'success'270 };271}272// Show SQL execution result273function showResult(message, type) {274 const sqlResult = document.getElementById('sql-result');275 sqlResult.className = `mt-4 p-3 rounded ${getStatusClass(type)}`;276 sqlResult.innerHTML = message;277 sqlResult.classList.remove('hidden');278 279 setTimeout(() => {280 sqlResult.classList.add('hidden');281 }, 5000);282}283 284// Get status class for styling285function getStatusClass(type) {286 switch (type) {287 case 'success': return 'status-success bg-green-900/20 border-green-500';288 case 'warning': return 'status-warning bg-yellow-900/20 border-yellow-500';289 case 'error': return 'status-error bg-red-900/20 border-red-500';290 default: return 'bg-gray-700';291 }292}293 294// Add new event295function addEvent(titulo, descripcion, tipo = 'info') {296 const evento = {297 titulo,298 descripcion,299 tipo,300 fecha: new Date().toLocaleTimeString('es-ES')301 };302 303 gameState.eventos.unshift(evento);304 if (gameState.eventos.length > 10) {305 gameState.eventos.pop();306 }307 308 renderEvents();309}310// Handle SELECT command (basic implementation)311function handleSelectCommand(command) {312 const upperCommand = command.toUpperCase();313 314 if (upperCommand.includes('FROM PRODUCTOS')) {315 let productos = [...gameState.productos];316 317 // Basic WHERE clause support318 if (upperCommand.includes('WHERE')) {319 const whereMatch = command.match(/WHERE\s+(.+)/i);320 if (whereMatch) {321 const condition = whereMatch[1].toLowerCase();322 323 if (condition.includes('stock <')) {324 const stockValue = parseInt(condition.match(/stock\s*<\s*(\d+)/i)[1]);325 productos = productos.filter(p => p.stock < stockValue);326 } else if (condition.includes('precio >')) {327 const precioValue = parseInt(condition.match(/precio\s*>\s*(\d+)/i)[1]);328 productos = productos.filter(p => p.precio > precioValue);329 } else if (condition.includes('id =')) {330 const idValue = parseInt(condition.match(/id\s*=\s*(\d+)/i)[1]);331 productos = productos.filter(p => p.id === idValue);332 }333 }334 }335 336 const result = productos.map(p => `ID: ${p.id}, Nombre: "${p.nombre}", Precio: ${p.precio}, Stock: ${p.stock}`).join('\n');337 return {338 message: `✅ Resultados de la consulta (${productos.length} registros):\n${result}`,339 type: 'success'340 };341 }342 else if (upperCommand.includes('FROM CLIENTES')) {343 const result = gameState.clientes.map(c => `ID: ${c.id}, Nombre: "${c.nombre}", Email: ${c.email}, Pedidos: ${c.pedidos.length}`).join('\n');344 return {345 message: `✅ Resultados de la consulta (${gameState.clientes.length} registros):\n${result}`,346 type: 'success'347 };348 }349 else {350 throw new Error('Consulta SELECT no soportada. Usa FROM Productos o FROM Clientes.');351 }352}353 354// Generate random events355function generateRandomEvent() {356 const events = [357 {358 titulo: '📦 ¡Nueva Oferta!',359 descripcion: 'Un proveedor ofrece 50 unidades de "Nintendo Switch" a precio especial.',360 tipo: 'success'361 },362 {363 titulo: '🔄 Devolución',364 descripcion: 'Un cliente devuelve una PlayStation 5. Stock actualizado.',365 tipo: 'warning'366 },367 {368 titulo: '🔥 Producto Popular',369 descripcion: 'Las ventas de Xbox Series X han aumentado. Considera aumentar el stock.',370 tipo: 'info'371 },372 {373 titulo: '⚠️ Stock Bajo',374 descripcion: 'Quedan pocas unidades de PlayStation 5. ¡Reabastece pronto!',375 tipo: 'error'376 },377 {378 titulo: '🎯 Nuevo Cliente',379 descripcion: 'Un nuevo cliente se ha registrado en tu tienda.',380 tipo: 'success'381 },382 {383 titulo: '💰 Oferta Especial',384 descripcion: 'Hoy es día de ofertas! Los clientes buscan descuentos.',385 tipo: 'info'386 }387 ];388 389 const randomEvent = events[Math.floor(Math.random() * events.length)];390 addEvent(randomEvent.titulo, randomEvent.descripcion, randomEvent.tipo);391}392// Start event generator393function startEventGenerator() {394 setInterval(() => {395 if (Math.random() > 0.7) { // 30% chance every 30 seconds396 generateRandomEvent();397 }398 }, 30000);399}400 401// Export for global access402window.executeSQL = executeSQL;403window.initGame = initGame;