From 90f2027671ad7d878b932ec9b97a5c9d65946a3b Mon Sep 17 00:00:00 2001 From: MariaJoseMontepequeZet Date: Mon, 24 Aug 2026 16:27:20 -0600 Subject: [PATCH 1/2] feat(sql): resolver ejercicio 73 --- .../maria-montepeque/ejercicio-73/README.md | 84 +++++++++++++++++++ .../ejercicio-73/ddl/schema.sql | 34 ++++++++ .../ejercicio-73/dml/inserts.sql | 56 +++++++++++++ .../ejercicio-73/dql/consultas.sql | 50 +++++++++++ .../ejercicio-73/evidencias/resultados.md | 67 +++++++++++++++ 5 files changed, 291 insertions(+) create mode 100644 resoluciones/maria-montepeque/ejercicio-73/README.md create mode 100644 resoluciones/maria-montepeque/ejercicio-73/ddl/schema.sql create mode 100644 resoluciones/maria-montepeque/ejercicio-73/dml/inserts.sql create mode 100644 resoluciones/maria-montepeque/ejercicio-73/dql/consultas.sql create mode 100644 resoluciones/maria-montepeque/ejercicio-73/evidencias/resultados.md diff --git a/resoluciones/maria-montepeque/ejercicio-73/README.md b/resoluciones/maria-montepeque/ejercicio-73/README.md new file mode 100644 index 00000000..926fd3f2 --- /dev/null +++ b/resoluciones/maria-montepeque/ejercicio-73/README.md @@ -0,0 +1,84 @@ +# Ejercicio 73: INSERT Nivel Aplicado + +**Nombre:** Maria Jose Montepeque +**Fecha:** 2026-08-24 + +## Tema central + +INSERT + +## Descripcion del problema + +Una bodega de dispositivos tecnologicos necesita llevar el inventario +de sus productos sin depender de un numero de stock que alguien tenga +que actualizar a mano. En vez de eso, cada entrada y cada salida de +bodega se registra como un movimiento, y el stock real de cualquier +producto se calcula sumando sus entradas y restando sus salidas: un +caso de negocio completo, con reporte final, propio del nivel +aplicado. + +## Tablas y relaciones + +- `categorias`: catalogo de categorias de producto. +- `productos`: catalogo de productos, cada uno de una categoria. +- `movimientos`: historial de entradas y salidas de bodega. + `categorias` 1—N `productos`; `productos` 1—N `movimientos`. + +## Uso de INSERT + +En `dml/inserts.sql`: + +1. `INSERT` de una sola fila: se registra la primera categoria. +2. `INSERT` multiple (`VALUES (...), (...)`): el resto de categorias, + los 5 productos, las 5 entradas iniciales de bodega y las 5 + salidas por ventas o uso interno. +3. `INSERT` omitiendo `tipo_movimiento`: el reabastecimiento de + Laptop Pro 14 se inserta sin indicar el tipo, y queda en su + `DEFAULT` (`'entrada'`). + +La consulta 5 en `dql/consultas.sql` es el reporte final del caso de +negocio: reconstruye el stock de cada producto solo a partir de los +`INSERT` de `movimientos`, sin que exista ninguna columna de stock +guardada aparte. Esto confirma que todos los `INSERT` cumplieron su +proposito. + +## Otras restricciones aplicadas + +- `PRIMARY KEY` autoincremental en las 3 tablas. +- `FOREIGN KEY`: `productos.id_categoria`, `movimientos.id_producto`. +- `NOT NULL` en todas las columnas obligatorias. +- `UNIQUE`: `categorias.nombre_categoria`, `productos.nombre_producto`. +- `CHECK`: `productos.precio_unitario >= 0`, + `movimientos.tipo_movimiento IN (...)`, `movimientos.cantidad > 0`. +- `DEFAULT` en `movimientos.tipo_movimiento` y `fecha_movimiento`. +- `PRAGMA foreign_keys = ON;` activado al inicio del script. + +## Casos que fallan / no recomendables (comentados en `dml/inserts.sql`) + +Uno por cada restriccion, validado con Python (`sqlite3`): + +- Repetir `nombre_producto` -> `UNIQUE constraint failed`. +- Apuntar a un `id_producto` que no existe -> `FOREIGN KEY constraint failed`. +- Registrar una `cantidad` negativa -> `CHECK constraint failed`. + +## Evidencias de ejecucion + +Scripts validados en orden (`ddl` -> `inserts` -> `consultas`) con +Python (modulo `sqlite3`), ya que no se tenia el binario `sqlite3` +disponible en el entorno. Detalle completo en +[`evidencias/resultados.md`](evidencias/resultados.md). + +- Datos finales: 3 categorias, 5 productos, 11 movimientos (6 + entradas, 5 salidas). Stock final verificado: Laptop Pro 14 = 12, + Laptop Air 13 = 6, Mouse Inalambrico = 38, Teclado Mecanico = 23, + Disco SSD 1TB = 16. + +## Como ejecutar + +```bash +sqlite3 ejercicio-73.db < ddl/schema.sql +sqlite3 ejercicio-73.db < dml/inserts.sql +sqlite3 ejercicio-73.db < dql/consultas.sql +``` + +No suba archivos `.db`, `.sqlite` ni `.sqlite3`. diff --git a/resoluciones/maria-montepeque/ejercicio-73/ddl/schema.sql b/resoluciones/maria-montepeque/ejercicio-73/ddl/schema.sql new file mode 100644 index 00000000..753b2d6f --- /dev/null +++ b/resoluciones/maria-montepeque/ejercicio-73/ddl/schema.sql @@ -0,0 +1,34 @@ +PRAGMA foreign_keys = ON; + +-- Ejercicio 73: INSERT Nivel Aplicado +-- Tema central: INSERT +-- Contexto: inventario de dispositivos tecnologicos en bodega. + +CREATE TABLE categorias ( + id_categoria INTEGER PRIMARY KEY AUTOINCREMENT, + nombre_categoria TEXT NOT NULL UNIQUE +); + +CREATE TABLE productos ( + id_producto INTEGER PRIMARY KEY AUTOINCREMENT, + nombre_producto TEXT NOT NULL UNIQUE, + id_categoria INTEGER NOT NULL, + precio_unitario REAL NOT NULL CHECK (precio_unitario >= 0), + + FOREIGN KEY (id_categoria) REFERENCES categorias (id_categoria) +); + +-- movimientos: el stock de cada producto no se guarda como columna +-- aparte, se calcula a partir de este historial de entradas y +-- salidas (ver consulta 5 en dql/consultas.sql, el caso de negocio +-- con reporte final propio del nivel aplicado). +CREATE TABLE movimientos ( + id_movimiento INTEGER PRIMARY KEY AUTOINCREMENT, + id_producto INTEGER NOT NULL, + tipo_movimiento TEXT NOT NULL DEFAULT 'entrada' + CHECK (tipo_movimiento IN ('entrada', 'salida')), + cantidad INTEGER NOT NULL CHECK (cantidad > 0), + fecha_movimiento TEXT NOT NULL DEFAULT (datetime('now')), + + FOREIGN KEY (id_producto) REFERENCES productos (id_producto) +); diff --git a/resoluciones/maria-montepeque/ejercicio-73/dml/inserts.sql b/resoluciones/maria-montepeque/ejercicio-73/dml/inserts.sql new file mode 100644 index 00000000..40021fc9 --- /dev/null +++ b/resoluciones/maria-montepeque/ejercicio-73/dml/inserts.sql @@ -0,0 +1,56 @@ +PRAGMA foreign_keys = ON; + +-- Ejercicio 73: INSERT Nivel Aplicado +-- Datos de prueba para validar el tema INSERT. + +-- 1. INSERT de una sola fila: se registra la primera categoria. +INSERT INTO categorias (nombre_categoria) VALUES + ('Laptops'); + +-- 2. INSERT multiple: el resto de categorias. +INSERT INTO categorias (nombre_categoria) VALUES + ('Perifericos'), + ('Almacenamiento'); + +-- 3. INSERT multiple de productos, con todas las columnas explicitas. +INSERT INTO productos (nombre_producto, id_categoria, precio_unitario) VALUES + ('Laptop Pro 14', 1, 8500.00), + ('Laptop Air 13', 1, 6200.00), + ('Mouse Inalambrico', 2, 150.00), + ('Teclado Mecanico', 2, 320.00), + ('Disco SSD 1TB', 3, 480.00); + +-- 4. INSERT multiple: entradas iniciales de bodega (una por +-- producto), con tipo_movimiento explicito. +INSERT INTO movimientos (id_producto, tipo_movimiento, cantidad) VALUES + (1, 'entrada', 10), + (2, 'entrada', 8), + (3, 'entrada', 50), + (4, 'entrada', 30), + (5, 'entrada', 20); + +-- 5. INSERT multiple: salidas por ventas o uso interno. +INSERT INTO movimientos (id_producto, tipo_movimiento, cantidad) VALUES + (1, 'salida', 3), + (3, 'salida', 12), + (4, 'salida', 7), + (5, 'salida', 4), + (2, 'salida', 2); + +-- 6. INSERT SIN indicar tipo_movimiento: se omite a proposito para +-- que quede en su DEFAULT ('entrada'). Reabastecimiento de laptops +-- Pro 14 despues de la salida anterior. +INSERT INTO movimientos (id_producto, cantidad) VALUES + (1, 5); + +-- Casos comentados que deben fallar (no ser recomendables), dejar +-- comentados: + +-- 1) Registro repetido: nombre_producto ya existe, viola el UNIQUE. +-- INSERT INTO productos (nombre_producto, id_categoria, precio_unitario) VALUES ('Laptop Pro 14', 1, 9000.00); + +-- 2) Relacion invalida: id_producto = 99 no existe, viola el FOREIGN KEY. +-- INSERT INTO movimientos (id_producto, tipo_movimiento, cantidad) VALUES (99, 'entrada', 5); + +-- 3) Valor fuera de rango: cantidad negativa, viola el CHECK. +-- INSERT INTO movimientos (id_producto, tipo_movimiento, cantidad) VALUES (2, 'entrada', -10); diff --git a/resoluciones/maria-montepeque/ejercicio-73/dql/consultas.sql b/resoluciones/maria-montepeque/ejercicio-73/dql/consultas.sql new file mode 100644 index 00000000..1cae3224 --- /dev/null +++ b/resoluciones/maria-montepeque/ejercicio-73/dql/consultas.sql @@ -0,0 +1,50 @@ +.headers on +.mode column + +-- Ejercicio 73: INSERT Nivel Aplicado +-- Consultas de validacion. + +-- 1. Mostrar todos los datos principales (movimientos con producto y +-- categoria). +SELECT m.id_movimiento, + p.nombre_producto, + c.nombre_categoria, + m.tipo_movimiento, + m.cantidad, + m.fecha_movimiento +FROM movimientos m +JOIN productos p ON p.id_producto = m.id_producto +JOIN categorias c ON c.id_categoria = p.id_categoria; + +-- 2. Consulta con WHERE: solo las salidas de bodega. +SELECT id_movimiento, id_producto, cantidad +FROM movimientos +WHERE tipo_movimiento = 'salida'; + +-- 3. Consulta con ORDER BY: movimientos ordenados por fecha. +SELECT id_movimiento, fecha_movimiento, tipo_movimiento, cantidad +FROM movimientos +ORDER BY fecha_movimiento; + +-- 4. Conteo o resumen: total de movimientos por tipo. +SELECT tipo_movimiento, COUNT(*) AS total +FROM movimientos +GROUP BY tipo_movimiento; + +-- 5. Caso de negocio con reporte final (nivel aplicado): el stock +-- real de cada producto no se guardo en ninguna columna, se calcula +-- sumando todas las entradas y restando todas las salidas. Esta +-- consulta demuestra que los INSERT de movimientos cumplieron su +-- proposito: reconstruyen el stock actual desde cero, solo con el +-- historial. +SELECT p.nombre_producto, + SUM( + CASE + WHEN m.tipo_movimiento = 'entrada' THEN m.cantidad + ELSE -m.cantidad + END + ) AS stock_actual +FROM productos p +JOIN movimientos m ON m.id_producto = p.id_producto +GROUP BY p.id_producto, p.nombre_producto +ORDER BY p.nombre_producto; diff --git a/resoluciones/maria-montepeque/ejercicio-73/evidencias/resultados.md b/resoluciones/maria-montepeque/ejercicio-73/evidencias/resultados.md new file mode 100644 index 00000000..99a87b14 --- /dev/null +++ b/resoluciones/maria-montepeque/ejercicio-73/evidencias/resultados.md @@ -0,0 +1,67 @@ +# Evidencias - Ejercicio 73 + +## Tema + +INSERT + +## Comandos ejecutados + +No se conto con el binario `sqlite3` en el entorno de trabajo, por lo que +la ejecucion se valido con Python (`sqlite3`), aplicando los mismos +scripts en el mismo orden: + +```bash +sqlite3 ejercicio-73.db < ddl/schema.sql +sqlite3 ejercicio-73.db < dml/inserts.sql +sqlite3 ejercicio-73.db < dql/consultas.sql +``` + +## Resultados + +Datos base tras `dml/inserts.sql`: 3 categorias, 5 productos y 11 +movimientos (6 entradas, 5 salidas). + +**Casos comentados verificados** (descomentados y ejecutados por +separado para confirmar que cada uno falla): + +- `INSERT INTO productos (nombre_producto, ...) VALUES ('Laptop Pro 14', ...);` → `UNIQUE constraint failed: productos.nombre_producto`. +- `INSERT INTO movimientos (id_producto, ...) VALUES (99, ...);` → `FOREIGN KEY constraint failed`. +- `INSERT INTO movimientos (..., cantidad) VALUES (..., -10);` → `CHECK constraint failed: cantidad > 0`. + +**4. Resumen: movimientos por tipo:** + +```text +tipo_movimiento total +entrada 6 +salida 5 +``` + +**5. Caso de negocio con reporte final: stock real de cada producto, +calculado sumando entradas y restando salidas (sin ninguna columna de +stock guardada):** + +```text +nombre_producto stock_actual +Disco SSD 1TB 16 +Laptop Air 13 6 +Laptop Pro 14 12 +Mouse Inalambrico 38 +Teclado Mecanico 23 +``` + +Verificacion manual de Laptop Pro 14: entrada 10, salida 3, entrada 5 +(sin indicar `tipo_movimiento`, quedo en `'entrada'` por `DEFAULT`) = +10 - 3 + 5 = 12. Coincide exactamente con el reporte. + +## Aprendizaje + +Ademas de `INSERT` de una fila, `INSERT` multiple y `INSERT` omitiendo +una columna con `DEFAULT` (vistos en los niveles basico e +intermedio), este ejercicio de nivel aplicado demostro un caso de +negocio completo: el stock de cada producto nunca se guarda como un +numero fijo que hay que mantener sincronizado a mano, se reconstruye +siempre desde el historial de `movimientos` con una consulta de +reporte. Esto significa que cada `INSERT` en `movimientos` es, en si +mismo, la unica fuente de verdad del inventario: si los `INSERT` estan +completos y correctos (protegidos por `CHECK` y `FOREIGN KEY`), el +reporte final siempre sera correcto sin necesidad de ningun `UPDATE`. From 0c52f92cfd6719931fd1cf616a9b2fe0955ba1d1 Mon Sep 17 00:00:00 2001 From: MariaJoseMontepequeZet Date: Mon, 24 Aug 2026 16:27:21 -0600 Subject: [PATCH 2/2] feat(sql): resolver solicitud SQL ejercicio 073 (clanes shooter) --- .../solicitudes-sql/ejercicio-073/README.md | 81 +++++++++++++++++++ .../ejercicio-073/analisis/requerimiento.md | 76 +++++++++++++++++ .../ejercicio-073/ddl/schema.sql | 58 +++++++++++++ .../ejercicio-073/diagramas/.gitkeep | 0 .../ejercicio-073/diagramas/diagrama-er.svg | 55 +++++++++++++ .../ejercicio-073/dml/inserts.sql | 75 +++++++++++++++++ .../ejercicio-073/dml/operaciones.sql | 25 ++++++ .../ejercicio-073/dql/consultas.sql | 44 ++++++++++ .../ejercicio-073/evidencias/resultados.md | 80 ++++++++++++++++++ 9 files changed, 494 insertions(+) create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-073/README.md create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-073/analisis/requerimiento.md create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-073/ddl/schema.sql create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-073/diagramas/.gitkeep create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-073/diagramas/diagrama-er.svg create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-073/dml/inserts.sql create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-073/dml/operaciones.sql create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-073/dql/consultas.sql create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-073/evidencias/resultados.md diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-073/README.md b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-073/README.md new file mode 100644 index 00000000..3737c4e3 --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-073/README.md @@ -0,0 +1,81 @@ +# Solicitud SQL - Ejercicio 073: Clanes Shooter + +**Nombre:** Maria Jose Montepeque +**Fecha:** 2026-08-24 + +## Solicitud del cliente + +Una plataforma de shooter administra clanes, scrims, mapas y +resultados. El cliente quiere evitar registros incompletos porque +despues no puede hacer reportes confiables. Pidio convertir esa +operacion en una base de datos que permita consultar datos, corregir +estados, registrar movimientos y sacar reportes utiles. + +## Que entendi de la solicitud + +El problema central no es "guardar resultados", es garantizar que +cada scrim tenga como maximo un resultado oficial: sin eso, cualquier +reporte de victorias o de actividad queda contaminado por registros +duplicados o incompletos. El nivel pedido (4, reportes y +agrupaciones) exige ademas `JOIN`, `GROUP BY`, `HAVING`, totales y +ranking. El detalle completo del analisis esta en +[analisis/requerimiento.md](analisis/requerimiento.md). + +## Que tablas cree y por que + +- `clanes`: catalogo de clanes registrados. +- `jugadores`: catalogo de jugadores, cada uno miembro de un clan. +- `mapas`: catalogo de mapas disponibles para scrims. +- `scrims`: tabla transaccional, cada enfrentamiento entre dos clanes + en un mapa. +- `resultados`: registro oficial del resultado de un scrim. Aqui esta + la restriccion que ataca el problema del cliente: un + `UNIQUE (id_scrim)` garantiza que un scrim nunca tenga mas de un + resultado. + +## Como se relacionan + +`clanes` 1:N `jugadores`; `clanes` 1:N `scrims` (como local y como +visitante); `mapas` 1:N `scrims`; `scrims` 1:1 `resultados`. El +diagrama esta en [diagramas/diagrama-er.svg](diagramas/diagrama-er.svg). + +## Que datos de prueba use + +4 clanes, 8 jugadores, 4 mapas, 5 scrims (4 marcados `jugado` en algun +momento, 1 `programado`) y 4 resultados, incluido uno cargado por +error para un scrim que despues se descubrio que habia que cancelar. +Tambien un `INSERT` comentado que reproduce exactamente el problema +del cliente (dos resultados para el mismo scrim) y debe fallar. +Detalle en [dml/inserts.sql](dml/inserts.sql). + +## Que operaciones de mantenimiento incluyo + +En [dml/operaciones.sql](dml/operaciones.sql): un `UPDATE` de estado +(el scrim que se cayo a la mitad pasa a `cancelado`) y un `DELETE` +controlado que limpia el resultado huerfano de ese scrim, sin tocar +ningun resultado de un scrim ya `jugado`. + +## Que consultas responden al cliente + +En [dql/consultas.sql](dql/consultas.sql): que scrims existen (JOIN +clan local-clan visitante-mapa), en que estado esta cada uno, que clan +jugo mas scrims, los scrims ordenados por fecha, y un reporte con +`GROUP BY` + `HAVING` de victorias por clan, para decidir quien +clasifica a playoffs. + +## Evidencias + +Resultados de ejecutar todo en orden, incluyendo la verificacion del +caso de duplicado y de las operaciones de mantenimiento, en +[evidencias/resultados.md](evidencias/resultados.md). + +## Como ejecutar + +```bash +sqlite3 ejercicio-073.db < ddl/schema.sql +sqlite3 ejercicio-073.db < dml/inserts.sql +sqlite3 ejercicio-073.db < dml/operaciones.sql +sqlite3 ejercicio-073.db < dql/consultas.sql +``` + +No suba archivos `.db`, `.sqlite`, `.sqlite3` ni `.dump`. diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-073/analisis/requerimiento.md b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-073/analisis/requerimiento.md new file mode 100644 index 00000000..808cddd8 --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-073/analisis/requerimiento.md @@ -0,0 +1,76 @@ +# Analisis del requerimiento - Ejercicio 073 + +## Solicitud entendida + +Una plataforma de shooter administra clanes, scrims (partidas de +practica entre clanes), mapas y resultados. El cliente quiere evitar +registros incompletos porque despues no puede hacer reportes +confiables: eso significa que el modelo debe impedir, desde el diseno, +que un scrim quede sin resultado claro o con resultados repetidos o +contradictorios. Se necesita una base de datos que permita consultar +datos, corregir estados, registrar movimientos y sacar reportes +utiles, como que clan gana mas scrims. + +## Entidades detectadas + +| Entidad | Por que existe | Atributos importantes | +| --- | --- | --- | +| clanes | Catalogo: cada clan registrado en la plataforma | nombre_clan (unico), region | +| jugadores | Catalogo: cada jugador, miembro de un clan | nickname (unico), id_clan | +| mapas | Catalogo: cada mapa disponible para scrims | nombre_mapa (unico), modo_juego | +| scrims | Tabla transaccional: enfrentamiento programado entre dos clanes en un mapa | fecha_scrim, estado | +| resultados | Registro oficial del resultado de un scrim, uno solo por scrim | marcador_local, marcador_visitante, id_clan_ganador | + +## Relaciones detectadas + +| Relacion | Tipo | Explicacion | +| --- | --- | --- | +| clanes -> jugadores | 1:N | Un clan tiene varios jugadores. | +| clanes -> scrims | 1:N (dos veces) | Un clan juega muchos scrims, como local o como visitante. | +| mapas -> scrims | 1:N | Un mapa se usa en muchos scrims. | +| scrims -> resultados | 1:1 | Cada scrim tiene, como mucho, un resultado oficial: ese es justo el problema de registros incompletos/duplicados que preocupa al cliente. | + +## Reglas de negocio + +Cada regla ataca directamente el problema central del cliente +(registros incompletos que no sirven para reportes confiables): + +- Regla 1 (relaciones invalidas): todo scrim debe apuntar a dos + clanes reales y a un mapa real; todo resultado debe apuntar a un + scrim real (`FOREIGN KEY` en cadena). +- Regla 2 (registros repetidos/incompletos): `clanes.nombre_clan`, + `jugadores.nickname` y `mapas.nombre_mapa` no se repiten (`UNIQUE`); + un scrim no puede tener mas de un resultado oficial + (`UNIQUE (id_scrim)` en `resultados`, que en la practica funciona + como una relacion 1:1). Esto es exactamente lo que evita el reporte + confiable que pidio el cliente. +- Regla 3 (valores fuera de rango): `marcador_local` y + `marcador_visitante` nunca pueden ser negativos (`CHECK`). +- Regla 4: un scrim nace `'programado'` y solo puede avanzar a + `'jugado'` o `'cancelado'` (`CHECK`); se corrige con `UPDATE`. +- Regla 5: un resultado solo tiene sentido para un scrim que ya se + jugo. Si un scrim se cancela y ya tenia un resultado cargado por + error, ese resultado se elimina; nunca se borra el resultado de un + scrim `'jugado'`, porque ya es un dato oficial de la liga. + +## Supuestos + +- El cliente no detallo si `id_clan_ganador` debe validarse contra los + dos clanes del scrim correspondiente; SQLite no permite un `CHECK` + que compare columnas de dos tablas distintas, asi que esa regla + queda documentada aqui como responsabilidad del proceso de carga + (siempre se inserta el ganador ya sabiendo que es local o + visitante), no como una restriccion de base de datos. +- Se asume que un jugador pertenece a un solo clan a la vez (no hay + historial de transferencias en el alcance de este nivel). +- Se asume que el marcador de un scrim empatado tambien es valido + (`marcador_local = marcador_visitante`); en ese caso + `id_clan_ganador` puede quedar en `NULL`. + +## Preguntas que responde la base de datos + +1. Que scrims existen, con que clanes y en que mapa. +2. Que scrims estan programados, jugados o cancelados. +3. Que clan jugo mas scrims (ranking de actividad). +4. Como se ordenan los scrims por fecha. +5. Que clan gano mas scrims, para decidir quien clasifica a playoffs. diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-073/ddl/schema.sql b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-073/ddl/schema.sql new file mode 100644 index 00000000..be4956f0 --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-073/ddl/schema.sql @@ -0,0 +1,58 @@ +PRAGMA foreign_keys = ON; + +-- Ejercicio 073: Clanes Shooter +-- Modelo: clanes -> jugadores (1:N); clanes -> scrims (1:N, como +-- local y como visitante); mapas -> scrims (1:N); scrims -> +-- resultados (1:1, con UNIQUE sobre id_scrim). El objetivo central es +-- evitar registros incompletos o duplicados, tal como lo pidio el +-- cliente. + +CREATE TABLE clanes ( + id_clan INTEGER PRIMARY KEY AUTOINCREMENT, + nombre_clan TEXT NOT NULL UNIQUE, + region TEXT NOT NULL +); + +CREATE TABLE jugadores ( + id_jugador INTEGER PRIMARY KEY AUTOINCREMENT, + nickname TEXT NOT NULL UNIQUE, + id_clan INTEGER NOT NULL, + + FOREIGN KEY (id_clan) REFERENCES clanes (id_clan) +); + +CREATE TABLE mapas ( + id_mapa INTEGER PRIMARY KEY AUTOINCREMENT, + nombre_mapa TEXT NOT NULL UNIQUE, + modo_juego TEXT NOT NULL CHECK (modo_juego IN ('busqueda', 'dominio', 'eliminacion')) +); + +CREATE TABLE scrims ( + id_scrim INTEGER PRIMARY KEY AUTOINCREMENT, + id_clan_local INTEGER NOT NULL, + id_clan_visitante INTEGER NOT NULL, + id_mapa INTEGER NOT NULL, + fecha_scrim TEXT NOT NULL, + estado TEXT NOT NULL DEFAULT 'programado' + CHECK (estado IN ('programado', 'jugado', 'cancelado')), + + FOREIGN KEY (id_clan_local) REFERENCES clanes (id_clan), + FOREIGN KEY (id_clan_visitante) REFERENCES clanes (id_clan), + FOREIGN KEY (id_mapa) REFERENCES mapas (id_mapa) +); + +-- resultados: el UNIQUE sobre id_scrim garantiza como maximo un +-- resultado oficial por scrim (relacion 1:1). Es la restriccion que +-- ataca directamente el problema del cliente: registros incompletos o +-- repetidos que arruinan los reportes. +CREATE TABLE resultados ( + id_resultado INTEGER PRIMARY KEY AUTOINCREMENT, + id_scrim INTEGER NOT NULL UNIQUE, + id_clan_ganador INTEGER, + marcador_local INTEGER NOT NULL CHECK (marcador_local >= 0), + marcador_visitante INTEGER NOT NULL CHECK (marcador_visitante >= 0), + fecha_registro TEXT NOT NULL DEFAULT (datetime('now')), + + FOREIGN KEY (id_scrim) REFERENCES scrims (id_scrim), + FOREIGN KEY (id_clan_ganador) REFERENCES clanes (id_clan) +); diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-073/diagramas/.gitkeep b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-073/diagramas/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-073/diagramas/diagrama-er.svg b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-073/diagramas/diagrama-er.svg new file mode 100644 index 00000000..14b1f802 --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-073/diagramas/diagrama-er.svg @@ -0,0 +1,55 @@ + + + + + clanes + id_clan PK + nombre_clan UNIQUE + region + + + jugadores + id_jugador PK + nickname UNIQUE, id_clan FK + + + mapas + id_mapa PK + nombre_mapa UNIQUE, modo_juego + + + scrims + id_scrim PK + id_clan_local FK + id_clan_visitante FK, id_mapa FK + estado CHECK + + + resultados + id_resultado PK + id_scrim FK UNIQUE (1:1) + id_clan_ganador FK + marcador_local, marcador_visitante + + + + 1:N + + + + 1:N + + + + 1:N + + + + 1:1 + diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-073/dml/inserts.sql b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-073/dml/inserts.sql new file mode 100644 index 00000000..975b0b00 --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-073/dml/inserts.sql @@ -0,0 +1,75 @@ +PRAGMA foreign_keys = ON; + +-- Ejercicio 073: Clanes Shooter +-- Datos base: 4 clanes, 8 jugadores, 4 mapas, 5 scrims (3 jugados, +-- 1 programado, 1 jugado-por-error que se corrige despues) y 4 +-- resultados (incluye el que se carga por error antes de saber que +-- el scrim se debia cancelar). + +INSERT INTO clanes (nombre_clan, region) VALUES + ('Furia Roja', 'Norte'), + ('Sombra Digital', 'Sur'), + ('Vertigo', 'Centro'), + ('Nova Tactica', 'Oeste'); + +INSERT INTO jugadores (nickname, id_clan) VALUES + ('RedHawk', 1), + ('CrimsonAce', 1), + ('NightByte', 2), + ('CipherX', 2), + ('SpinOut', 3), + ('EchoDrift', 3), + ('NovaBlast', 4), + ('QuantumRay', 4); + +INSERT INTO mapas (nombre_mapa, modo_juego) VALUES + ('Bunker Norte', 'busqueda'), + ('Zona Industrial', 'dominio'), + ('Puerto Fantasma', 'eliminacion'), + ('Complejo Alfa', 'busqueda'); + +-- Scrim 1: Furia Roja (local) vs Sombra Digital (visitante), jugado. +INSERT INTO scrims (id_clan_local, id_clan_visitante, id_mapa, fecha_scrim, estado) VALUES + (1, 2, 1, '2026-08-01', 'jugado'); + +-- Scrim 2: Sombra Digital (local) vs Vertigo (visitante), jugado. +INSERT INTO scrims (id_clan_local, id_clan_visitante, id_mapa, fecha_scrim, estado) VALUES + (2, 3, 2, '2026-08-03', 'jugado'); + +-- Scrim 3: Vertigo (local) vs Nova Tactica (visitante), jugado. +INSERT INTO scrims (id_clan_local, id_clan_visitante, id_mapa, fecha_scrim, estado) VALUES + (3, 4, 3, '2026-08-05', 'jugado'); + +-- Scrim 4: Furia Roja vs Vertigo, todavia no se juega. +INSERT INTO scrims (id_clan_local, id_clan_visitante, id_mapa, fecha_scrim, estado) VALUES + (1, 3, 4, '2026-08-08', 'programado'); + +-- Scrim 5: se marco 'jugado' y se cargo su resultado, pero el +-- servidor se cayo a la mitad y el resultado se anulo despues. Se +-- corrige en dml/operaciones.sql. +INSERT INTO scrims (id_clan_local, id_clan_visitante, id_mapa, fecha_scrim, estado) VALUES + (4, 2, 1, '2026-08-02', 'jugado'); + +-- Resultado del scrim 1: gana Furia Roja. +INSERT INTO resultados (id_scrim, id_clan_ganador, marcador_local, marcador_visitante) VALUES + (1, 1, 5, 2); + +-- Resultado del scrim 2: empate, sin ganador. +INSERT INTO resultados (id_scrim, id_clan_ganador, marcador_local, marcador_visitante) VALUES + (2, NULL, 3, 3); + +-- Resultado del scrim 3: gana Nova Tactica (visitante). +INSERT INTO resultados (id_scrim, id_clan_ganador, marcador_local, marcador_visitante) VALUES + (3, 4, 1, 4); + +-- Resultado del scrim 5, cargado antes de saber que el servidor se +-- habia caido. Quedara huerfano cuando el scrim se marque +-- 'cancelado' en dml/operaciones.sql, y se elimina ahi mismo. +INSERT INTO resultados (id_scrim, id_clan_ganador, marcador_local, marcador_visitante) VALUES + (5, NULL, 2, 2); + +-- Caso comentado que debe fallar (queda comentado): registrar un +-- segundo resultado para el scrim 1, exactamente el problema que +-- describio el cliente (registros duplicados que arruinan los +-- reportes). El UNIQUE (id_scrim) lo bloquea. +-- INSERT INTO resultados (id_scrim, id_clan_ganador, marcador_local, marcador_visitante) VALUES (1, 1, 5, 2); diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-073/dml/operaciones.sql b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-073/dml/operaciones.sql new file mode 100644 index 00000000..7eb9fb32 --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-073/dml/operaciones.sql @@ -0,0 +1,25 @@ +PRAGMA foreign_keys = ON; + +-- Ejercicio 073: Clanes Shooter +-- Operaciones de mantenimiento sobre los datos base. + +-- 1 UPDATE de estado: se confirma que el scrim 5 se cayo a la mitad y +-- su resultado se anula. +UPDATE scrims +SET estado = 'cancelado' +WHERE id_scrim = 5 AND estado = 'jugado'; + +-- 1 DELETE controlado: el resultado del scrim 5 quedo huerfano apenas +-- se marco 'cancelado' (ya no representa un resultado oficial). Solo +-- se borran resultados de scrims 'cancelado'; un scrim 'jugado' nunca +-- pierde su resultado por este DELETE. +DELETE FROM resultados +WHERE id_scrim IN ( + SELECT id_scrim FROM scrims WHERE estado = 'cancelado' +); + +-- Caso que debe fallar / no ser recomendable (queda comentado): +-- borrar el resultado de un scrim que ya quedo 'jugado' (dato oficial +-- de la liga). El DELETE de arriba solo alcanza scrims 'cancelado' +-- por diseno. +-- DELETE FROM resultados WHERE id_scrim = 1; diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-073/dql/consultas.sql b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-073/dql/consultas.sql new file mode 100644 index 00000000..9247048b --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-073/dql/consultas.sql @@ -0,0 +1,44 @@ +.headers on +.mode column + +-- Ejercicio 073: Clanes Shooter +-- Consultas que responden preguntas reales del cliente. + +-- 1. Que registros principales existen: todos los scrims con sus +-- clanes y su mapa. +SELECT s.id_scrim, + cloc.nombre_clan AS clan_local, + cvis.nombre_clan AS clan_visitante, + m.nombre_mapa, + s.fecha_scrim, + s.estado +FROM scrims s +JOIN clanes cloc ON cloc.id_clan = s.id_clan_local +JOIN clanes cvis ON cvis.id_clan = s.id_clan_visitante +JOIN mapas m ON m.id_mapa = s.id_mapa; + +-- 2. Que scrims estan programados, jugados o cancelados. +SELECT id_scrim, fecha_scrim, estado +FROM scrims +ORDER BY estado; + +-- 3. Que clan jugo mas scrims (ranking de actividad). +SELECT c.nombre_clan, COUNT(*) AS total_scrims +FROM clanes c +JOIN scrims s ON s.id_clan_local = c.id_clan OR s.id_clan_visitante = c.id_clan +GROUP BY c.id_clan, c.nombre_clan +ORDER BY total_scrims DESC, c.nombre_clan; + +-- 4. Scrims ordenados por fecha. +SELECT id_scrim, fecha_scrim, estado +FROM scrims +ORDER BY fecha_scrim; + +-- 5. Reporte para decision de negocio: victorias por clan, para saber +-- quien clasifica a playoffs (GROUP BY + HAVING). +SELECT c.nombre_clan, COUNT(*) AS victorias +FROM resultados r +JOIN clanes c ON c.id_clan = r.id_clan_ganador +GROUP BY c.id_clan, c.nombre_clan +HAVING COUNT(*) >= 1 +ORDER BY victorias DESC, c.nombre_clan; diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-073/evidencias/resultados.md b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-073/evidencias/resultados.md new file mode 100644 index 00000000..96c56b02 --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-073/evidencias/resultados.md @@ -0,0 +1,80 @@ +# Evidencias - Solicitudes SQL - Ejercicio 073 (Clanes Shooter) + +## Comandos ejecutados + +No se conto con el binario `sqlite3` en el entorno de trabajo, por lo que +la ejecucion se valido con Python (`sqlite3`), aplicando los mismos +scripts en el mismo orden: + +```bash +sqlite3 ejercicio-073.db < ddl/schema.sql +sqlite3 ejercicio-073.db < dml/inserts.sql +sqlite3 ejercicio-073.db < dml/operaciones.sql +sqlite3 ejercicio-073.db < dql/consultas.sql +``` + +## Resultados + +Datos base tras `dml/inserts.sql`: 4 clanes, 8 jugadores, 4 mapas, 5 +scrims (4 `jugado`, 1 `programado`) y 4 resultados (incluye el +cargado por error para el scrim que se debia cancelar). + +**Caso comentado verificado** (el problema central del cliente): + +- `INSERT INTO resultados (id_scrim, ...) VALUES (1, ...);` (segundo resultado para el scrim 1) → `UNIQUE constraint failed: resultados.id_scrim`. + +**1. Todos los scrims, con JOIN a clan local, clan visitante y mapa:** + +```text +id_scrim | clan_local | clan_visitante | nombre_mapa | fecha_scrim | estado +1 | Furia Roja | Sombra Digital | Bunker Norte | 2026-08-01 | jugado +2 | Sombra Digital | Vertigo | Zona Industrial | 2026-08-03 | jugado +3 | Vertigo | Nova Tactica | Puerto Fantasma | 2026-08-05 | jugado +4 | Furia Roja | Vertigo | Complejo Alfa | 2026-08-08 | programado +5 | Nova Tactica | Sombra Digital | Bunker Norte | 2026-08-02 | cancelado +``` + +**2. Scrims por estado:** ver tabla completa arriba (el scrim 5 ya +aparece `cancelado`, se corrigio con el `UPDATE` de +`dml/operaciones.sql`). + +**3. Clan con mas scrims jugados (actividad total, local + +visitante):** + +```text +nombre_clan total_scrims +Sombra Digital 3 +Vertigo 3 +Furia Roja 2 +Nova Tactica 2 +``` + +**4. Scrims ordenados por fecha:** de 2026-08-01 a 2026-08-08. + +**5. Victorias por clan (reporte de clasificacion a playoffs):** + +```text +nombre_clan victorias +Furia Roja 1 +Nova Tactica 1 +``` + +(El scrim 2 termino en empate, sin ganador, por eso Sombra Digital y +Vertigo no aparecen en este reporte aunque jugaron mas scrims.) + +## Operaciones de mantenimiento verificadas + +- `UPDATE scrims SET estado = 'cancelado' WHERE id_scrim = 5 ...;` → el scrim de Bunker Norte del 2026-08-02 se anulo despues de confirmarse la caida del servidor. +- **DELETE controlado**: se elimino el unico resultado que habia quedado huerfano (el del scrim 5), apenas se marco `cancelado`. Total de resultados: 4 -> 3. Ningun resultado de un scrim `jugado` se toco. + +## Aprendizaje + +El `UNIQUE (id_scrim)` en `resultados` es la restriccion que resuelve +directamente el problema que trajo el cliente: ya no es posible +registrar dos resultados para el mismo scrim, lo que garantiza que +cualquier reporte que se construya sobre esta tabla sea confiable. El +`DELETE` controlado solo alcanza resultados de scrims `cancelado`, +nunca de uno `jugado` cuyo resultado ya es un dato oficial de la liga. +El reporte de victorias por clan (`GROUP BY` + `HAVING`) demuestra que +un modelo sin registros incompletos permite responder con confianza +una pregunta real del negocio: quien clasifica a playoffs.