From 77d69d9b6e372e33fe1ff2c580073fb32ea91075 Mon Sep 17 00:00:00 2001 From: MariaJoseMontepequeZet Date: Mon, 24 Aug 2026 17:30:30 -0600 Subject: [PATCH 1/2] feat(sql): resolver ejercicio 78 --- .../maria-montepeque/ejercicio-78/README.md | 83 +++++++++++++++++++ .../ejercicio-78/ddl/schema.sql | 32 +++++++ .../ejercicio-78/dml/inserts.sql | 53 ++++++++++++ .../ejercicio-78/dql/consultas.sql | 43 ++++++++++ .../ejercicio-78/evidencias/resultados.md | 62 ++++++++++++++ 5 files changed, 273 insertions(+) create mode 100644 resoluciones/maria-montepeque/ejercicio-78/README.md create mode 100644 resoluciones/maria-montepeque/ejercicio-78/ddl/schema.sql create mode 100644 resoluciones/maria-montepeque/ejercicio-78/dml/inserts.sql create mode 100644 resoluciones/maria-montepeque/ejercicio-78/dql/consultas.sql create mode 100644 resoluciones/maria-montepeque/ejercicio-78/evidencias/resultados.md diff --git a/resoluciones/maria-montepeque/ejercicio-78/README.md b/resoluciones/maria-montepeque/ejercicio-78/README.md new file mode 100644 index 00000000..6998a291 --- /dev/null +++ b/resoluciones/maria-montepeque/ejercicio-78/README.md @@ -0,0 +1,83 @@ +# Ejercicio 78: DELETE Nivel Intermedio + +**Nombre:** Maria Jose Montepeque +**Fecha:** 2026-08-24 + +## Tema central + +DELETE + +## Descripcion del problema + +Un sistema de registro de campers necesita limpiar inscripciones +duplicadas y canceladas sin arriesgar el resto de los datos, y tambien +poder descontinuar una ruta de entrenamiento sin perder el historial +de quien esta inscrito en ella. + +## Tablas y relaciones + +- `campers`: catalogo de campers registrados. +- `rutas`: catalogo de rutas, con una bandera `activa` para la baja + logica. +- `inscripciones`: relaciona un camper con una ruta. `campers` 1—N + `inscripciones`; `rutas` 1—N `inscripciones`. + +## Uso de DELETE + +En `dml/inserts.sql`: + +1. `DELETE` de una sola fila: Mario Ixtabalan quedo inscrito dos veces + en Cumbre Extrema por error de digitacion; se elimina solo la copia + duplicada con `WHERE id_inscripcion = 3`. +2. `DELETE` multiple: todas las inscripciones `'cancelada'` de + cualquier ruta se eliminan de una sola vez con + `WHERE estado = 'cancelada'`, sin listar cada id a mano. Este es el + diferenciador de nivel intermedio frente al DELETE de una sola fila + del nivel basico. +3. Baja logica (sin `DELETE`): Cumbre Extrema se descontinua, pero + todavia tiene inscripciones activas. En vez de intentar borrarla, + se marca `activa = 0` con `UPDATE`. + +La consulta 5 en `dql/consultas.sql` confirma que ya no queda ninguna +inscripcion cancelada y que el total final es el esperado. + +## Otras restricciones aplicadas + +- `PRIMARY KEY` autoincremental en las 3 tablas. +- `FOREIGN KEY`: `inscripciones.id_camper`, `inscripciones.id_ruta`. +- `NOT NULL` en todas las columnas obligatorias. +- `UNIQUE`: `campers.email`, `rutas.nombre_ruta`. +- `CHECK`: `rutas.cupo_maximo > 0`, `rutas.activa IN (0, 1)`, + `inscripciones.estado IN (...)`. +- `DEFAULT` en `rutas.cupo_maximo`, `rutas.activa`, + `inscripciones.estado` y `fecha_inscripcion`. +- `PRAGMA foreign_keys = ON;` activado al inicio del script. + +## Caso que falla / no recomendable (comentado en `dml/inserts.sql`) + +`DELETE FROM rutas WHERE id_ruta = 1;` falla porque Cumbre Extrema +todavia tiene inscripciones activas que dependen de ella por +`FOREIGN KEY`. Se valido con Python (`sqlite3`): lanza +`FOREIGN KEY constraint failed`. Esto justifica usar baja logica +(`UPDATE activa = 0`) en vez de `DELETE` para rutas con inscripciones +vigentes. + +## 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 inscripciones (todas activas), Cumbre Extrema dada + de baja logica. + +## Como ejecutar + +```bash +sqlite3 ejercicio-78.db < ddl/schema.sql +sqlite3 ejercicio-78.db < dml/inserts.sql +sqlite3 ejercicio-78.db < dql/consultas.sql +``` + +No suba archivos `.db`, `.sqlite` ni `.sqlite3`. diff --git a/resoluciones/maria-montepeque/ejercicio-78/ddl/schema.sql b/resoluciones/maria-montepeque/ejercicio-78/ddl/schema.sql new file mode 100644 index 00000000..49b558ae --- /dev/null +++ b/resoluciones/maria-montepeque/ejercicio-78/ddl/schema.sql @@ -0,0 +1,32 @@ +PRAGMA foreign_keys = ON; + +-- Ejercicio 78: DELETE Nivel Intermedio +-- Tema central: DELETE +-- Contexto: registro de campers inscritos en rutas de entrenamiento. + +CREATE TABLE campers ( + id_camper INTEGER PRIMARY KEY AUTOINCREMENT, + nombre TEXT NOT NULL, + email TEXT NOT NULL UNIQUE +); + +-- rutas: "activa" es la bandera de baja logica. Una ruta con +-- inscripciones todavia activas no se puede borrar de verdad. +CREATE TABLE rutas ( + id_ruta INTEGER PRIMARY KEY AUTOINCREMENT, + nombre_ruta TEXT NOT NULL UNIQUE, + cupo_maximo INTEGER NOT NULL DEFAULT 10 CHECK (cupo_maximo > 0), + activa INTEGER NOT NULL DEFAULT 1 CHECK (activa IN (0, 1)) +); + +CREATE TABLE inscripciones ( + id_inscripcion INTEGER PRIMARY KEY AUTOINCREMENT, + id_camper INTEGER NOT NULL, + id_ruta INTEGER NOT NULL, + estado TEXT NOT NULL DEFAULT 'activa' + CHECK (estado IN ('activa', 'completada', 'cancelada')), + fecha_inscripcion TEXT NOT NULL DEFAULT (datetime('now')), + + FOREIGN KEY (id_camper) REFERENCES campers (id_camper), + FOREIGN KEY (id_ruta) REFERENCES rutas (id_ruta) +); diff --git a/resoluciones/maria-montepeque/ejercicio-78/dml/inserts.sql b/resoluciones/maria-montepeque/ejercicio-78/dml/inserts.sql new file mode 100644 index 00000000..e42efcd9 --- /dev/null +++ b/resoluciones/maria-montepeque/ejercicio-78/dml/inserts.sql @@ -0,0 +1,53 @@ +PRAGMA foreign_keys = ON; + +-- Ejercicio 78: DELETE Nivel Intermedio +-- Datos de prueba y DELETE de validacion. + +INSERT INTO campers (nombre, email) VALUES + ('Karen Solis', 'karen.solis@campus.com'), + ('Mario Ixtabalan', 'mario.ixtabalan@campus.com'), + ('Ana Gomez', 'ana.gomez@campus.com'), + ('Luis Marroquin', 'luis.marroquin@campus.com'), + ('Rosa Chavez', 'rosa.chavez@campus.com'), + ('Diego Paz', 'diego.paz@campus.com'); + +INSERT INTO rutas (nombre_ruta) VALUES + ('Cumbre Extrema'), + ('Sendero del Canon'), + ('Ruta del Volcan'); + +INSERT INTO inscripciones (id_camper, id_ruta, estado) VALUES + (1, 1, 'activa'), + (2, 1, 'activa'), + (2, 1, 'activa'), + (3, 2, 'cancelada'), + (4, 2, 'cancelada'), + (5, 3, 'activa'), + (6, 1, 'cancelada'); + +-- 1. DELETE de una sola fila: Mario Ixtabalan quedo inscrito dos +-- veces en Cumbre Extrema por error de digitacion. Se elimina la +-- copia duplicada (id_inscripcion = 3), con WHERE por id especifico. +DELETE FROM inscripciones +WHERE id_inscripcion = 3; + +-- 2. DELETE multiple: se limpian de una sola vez todas las +-- inscripciones canceladas de cualquier ruta, con un solo DELETE y un +-- WHERE por estado (no un id a la vez). +DELETE FROM inscripciones +WHERE estado = 'cancelada'; + +-- 3. Baja logica (no DELETE): Cumbre Extrema se descontinua, pero +-- Karen Solis y Mario Ixtabalan todavia tienen inscripciones activas +-- ahi. En vez de intentar borrar la ruta, se marca como inactiva. +UPDATE rutas +SET activa = 0 +WHERE id_ruta = 1; + +-- Caso comentado que debe fallar (no ser recomendable), dejar +-- comentado: intentar el DELETE fisico de una ruta que todavia tiene +-- inscripciones activas asociadas. SQLite, con +-- PRAGMA foreign_keys = ON, no lo permite. Esto es justo lo que +-- justifica usar baja logica (UPDATE activa = 0) en vez de DELETE +-- para rutas. +-- DELETE FROM rutas WHERE id_ruta = 1; diff --git a/resoluciones/maria-montepeque/ejercicio-78/dql/consultas.sql b/resoluciones/maria-montepeque/ejercicio-78/dql/consultas.sql new file mode 100644 index 00000000..52f655d0 --- /dev/null +++ b/resoluciones/maria-montepeque/ejercicio-78/dql/consultas.sql @@ -0,0 +1,43 @@ +.headers on +.mode column + +-- Ejercicio 78: DELETE Nivel Intermedio +-- Consultas de validacion. + +-- 1. Mostrar todos los datos principales (inscripciones con camper y +-- ruta). +SELECT i.id_inscripcion, + c.nombre AS camper, + r.nombre_ruta, + i.estado +FROM inscripciones i +JOIN campers c ON c.id_camper = i.id_camper +JOIN rutas r ON r.id_ruta = i.id_ruta; + +-- 2. Consulta con WHERE: solo las inscripciones activas. +SELECT id_inscripcion, id_camper, id_ruta +FROM inscripciones +WHERE estado = 'activa'; + +-- 3. Consulta con ORDER BY: inscripciones ordenadas por fecha. +SELECT id_inscripcion, fecha_inscripcion, estado +FROM inscripciones +ORDER BY fecha_inscripcion; + +-- 4. Conteo o resumen: total de inscripciones por ruta. +SELECT id_ruta, COUNT(*) AS total_inscripciones +FROM inscripciones +GROUP BY id_ruta; + +-- 5. Validacion especifica de DELETE: ya no queda ninguna +-- inscripcion 'cancelada' (se borraron todas de una vez), ni la copia +-- duplicada de Mario Ixtabalan. Solo quedan las 3 inscripciones +-- 'activa' originales. +SELECT COUNT(*) AS canceladas_restantes +FROM inscripciones +WHERE estado = 'cancelada'; +-- Debe devolver 0: el DELETE multiple elimino todas las canceladas. + +SELECT COUNT(*) AS total_restante +FROM inscripciones; +-- Debe devolver 3: empezaron 7, se borro 1 duplicado y 3 canceladas. diff --git a/resoluciones/maria-montepeque/ejercicio-78/evidencias/resultados.md b/resoluciones/maria-montepeque/ejercicio-78/evidencias/resultados.md new file mode 100644 index 00000000..958aa4f7 --- /dev/null +++ b/resoluciones/maria-montepeque/ejercicio-78/evidencias/resultados.md @@ -0,0 +1,62 @@ +# Evidencias - Ejercicio 78 + +## Tema + +DELETE + +## 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-78.db < ddl/schema.sql +sqlite3 ejercicio-78.db < dml/inserts.sql +sqlite3 ejercicio-78.db < dql/consultas.sql +``` + +## Resultados + +Estado final tras `dml/inserts.sql` (7 inscripciones iniciales, 1 +duplicado y 3 canceladas eliminadas): + +```text +id_inscripcion | id_camper | id_ruta | estado +1 | 1 | 1 | activa +2 | 2 | 1 | activa +6 | 5 | 3 | activa +``` + +```text +rutas: +id_ruta | nombre_ruta | cupo_maximo | activa +1 | Cumbre Extrema | 10 | 0 +2 | Sendero del Canon | 10 | 1 +3 | Ruta del Volcan | 10 | 1 +``` + +**Caso comentado verificado:** + +- `DELETE FROM rutas WHERE id_ruta = 1;` → `FOREIGN KEY constraint failed` (Cumbre Extrema todavia tiene 2 inscripciones activas). + +**5. Validacion especifica de DELETE:** + +```text +5a. canceladas_restantes: 0 -- el DELETE multiple elimino las 3 de un solo golpe. +5b. total_restante: 3 -- empezaron 7, se borro 1 duplicado y 3 canceladas. +``` + +## Aprendizaje + +`DELETE` con `WHERE` por id especifico elimina exactamente una fila +(la copia duplicada de Mario Ixtabalan), mientras que `DELETE` con +`WHERE estado = 'cancelada'` elimina todas las filas que cumplen esa +condicion en una sola sentencia, sin importar cuantas sean ni en que +ruta esten: eso es lo que diferencia el nivel intermedio del basico. +Igual que con los productos del ejercicio anterior, `DELETE` no +siempre es la herramienta correcta para una tabla que todavia tiene +dependientes: cuando una ruta sigue teniendo inscripciones activas, +SQLite rechaza el `DELETE` fisico por `FOREIGN KEY`, y ahi es donde +conviene la baja logica (`UPDATE activa = 0`) para dar de baja la +ruta sin perder el historial de quien esta inscrito en ella. From 505881180e113bfc00f63b5538759529b64c6770 Mon Sep 17 00:00:00 2001 From: MariaJoseMontepequeZet Date: Mon, 24 Aug 2026 17:30:31 -0600 Subject: [PATCH 2/2] feat(sql): resolver solicitud SQL ejercicio 078 (torneo esports) --- .../solicitudes-sql/ejercicio-078/README.md | 86 +++++++++++++++++++ .../ejercicio-078/analisis/requerimiento.md | 74 ++++++++++++++++ .../ejercicio-078/ddl/schema.sql | 55 ++++++++++++ .../ejercicio-078/diagramas/.gitkeep | 0 .../ejercicio-078/diagramas/diagrama-er.svg | 58 +++++++++++++ .../ejercicio-078/dml/inserts.sql | 82 ++++++++++++++++++ .../ejercicio-078/dml/operaciones.sql | 37 ++++++++ .../ejercicio-078/dql/consultas.sql | 48 +++++++++++ .../ejercicio-078/evidencias/resultados.md | 72 ++++++++++++++++ 9 files changed, 512 insertions(+) create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-078/README.md create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-078/analisis/requerimiento.md create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-078/ddl/schema.sql create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-078/diagramas/.gitkeep create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-078/diagramas/diagrama-er.svg create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-078/dml/inserts.sql create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-078/dml/operaciones.sql create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-078/dql/consultas.sql create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-078/evidencias/resultados.md diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-078/README.md b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-078/README.md new file mode 100644 index 00000000..f504c7cc --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-078/README.md @@ -0,0 +1,86 @@ +# Solicitud SQL - Ejercicio 078: Torneo Esports + +**Nombre:** Maria Jose Montepeque +**Fecha:** 2026-08-24 + +## Solicitud del cliente + +Una organizacion de esports registra equipos, jugadores, partidas y +puntos. El cliente quiere consultar rankings, totales y casos +pendientes desde la base de datos. 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 + +"Casos pendientes" se traduce en partidas `programada`: el cliente +necesita poder distinguirlas de las que ya se jugaron o se +cancelaron. Y "consultar rankings... desde la base de datos" sugiere +que el ranking no se calcula siempre al vuelo, sino que vive en su +propia tabla y se corrige con `UPDATE`. 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 + +- `equipos`: catalogo de equipos del torneo. +- `jugadores`: catalogo de jugadores, cada uno de un equipo. +- `partidas`: tabla transaccional, con `estado` para distinguir casos + pendientes de partidas ya resueltas. +- `estadisticas`: detalle de cada partida. Aqui esta el + `UNIQUE (id_partida, id_jugador)` que impide cargar dos veces al + mismo jugador en la misma partida. +- `ranking`: una fila por equipo (`UNIQUE (id_equipo)`), que se + corrige con `UPDATE` a partir de las estadisticas reales. + +## Como se relacionan + +`equipos` 1:N `jugadores`; `equipos` 1:N `partidas` (como local y como +visitante); `partidas` 1:N `estadisticas`; `jugadores` 1:N +`estadisticas`; `equipos` 1:1 `ranking`. El diagrama esta en +[diagramas/diagrama-er.svg](diagramas/diagrama-er.svg). + +## Que datos de prueba use + +3 equipos, 9 jugadores, 4 partidas (3 marcadas `jugada` en algun +momento, 1 `programada` como caso pendiente), 14 filas de +estadisticas (incluye 2 cargadas por error para una partida que +despues se descubrio que habia que cancelar) y el ranking inicial en +0. Tambien un `INSERT` comentado que reproduce el problema de cargar +dos veces al mismo jugador en la misma partida 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 +(la partida que fallo por el servidor pasa a `cancelada`), un +`DELETE` controlado (multiple) que limpia las estadisticas huerfanas +de esa partida, y un `UPDATE` que recalcula el ranking de los 3 +equipos a partir de las partidas realmente jugadas. + +## Que consultas responden al cliente + +En [dql/consultas.sql](dql/consultas.sql): que estadisticas existen +(JOIN jugador-partida), que partidas estan pendientes, jugadas o +canceladas, que jugador tiene mas actividad, el ranking final +ordenado, y un reporte con `GROUP BY` + `HAVING` de que equipos +superan un puntaje minimo, para decidir quienes avanzan a la +siguiente fase. + +## 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-078.db < ddl/schema.sql +sqlite3 ejercicio-078.db < dml/inserts.sql +sqlite3 ejercicio-078.db < dml/operaciones.sql +sqlite3 ejercicio-078.db < dql/consultas.sql +``` + +No suba archivos `.db`, `.sqlite`, `.sqlite3` ni `.dump`. diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-078/analisis/requerimiento.md b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-078/analisis/requerimiento.md new file mode 100644 index 00000000..62783257 --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-078/analisis/requerimiento.md @@ -0,0 +1,74 @@ +# Analisis del requerimiento - Ejercicio 078 + +## Solicitud entendida + +Una organizacion de esports registra equipos, jugadores, partidas y +puntos. El cliente quiere consultar rankings, totales y casos +pendientes desde la base de datos: eso significa que el modelo debe +distinguir claramente que partidas ya se jugaron, cuales siguen +pendientes (programadas) y mantener un ranking de equipos que se +pueda consultar directamente, sin recalcularlo a mano cada vez. + +## Entidades detectadas + +| Entidad | Por que existe | Atributos importantes | +| --- | --- | --- | +| equipos | Catalogo: cada equipo del torneo | nombre_equipo (unico), region | +| jugadores | Catalogo: cada jugador, miembro de un equipo | nickname (unico), id_equipo | +| partidas | Tabla transaccional: enfrentamiento entre dos equipos | fecha_partida, estado | +| estadisticas | Detalle de cada partida: puntos de cada jugador que participo | puntos | +| ranking | Resumen: puntos totales acumulados por equipo | puntos_totales | + +## Relaciones detectadas + +| Relacion | Tipo | Explicacion | +| --- | --- | --- | +| equipos -> jugadores | 1:N | Un equipo tiene varios jugadores. | +| equipos -> partidas | 1:N (dos veces) | Un equipo juega muchas partidas, como local o como visitante. | +| partidas -> estadisticas | 1:N | Una partida tiene una fila de estadisticas por cada jugador que participo. | +| jugadores -> estadisticas | 1:N | Un jugador participa en muchas partidas distintas. | +| equipos -> ranking | 1:1 | Cada equipo tiene una sola fila de ranking, que se corrige con UPDATE. | + +## Reglas de negocio + +- Regla 1 (relaciones invalidas): toda estadistica debe apuntar a una + partida y a un jugador reales (`FOREIGN KEY` en cadena). +- Regla 2 (registros repetidos): `equipos.nombre_equipo` y + `jugadores.nickname` no se repiten (`UNIQUE`); un jugador no puede + tener dos filas de estadisticas en la misma partida + (`UNIQUE (id_partida, id_jugador)`); cada equipo tiene una sola fila + de ranking (`UNIQUE (id_equipo)` en `ranking`). +- Regla 3 (valores fuera de rango): `estadisticas.puntos` y + `ranking.puntos_totales` nunca pueden ser negativos (`CHECK`). +- Regla 4: una partida nace `'programada'` (caso pendiente) y avanza a + `'jugada'` o `'cancelada'` (`CHECK`); se corrige con `UPDATE`. +- Regla 5: las estadisticas solo tienen sentido para una partida que + ya se jugo. Si una partida se cancela y ya tenia estadisticas + cargadas por error, esas filas se eliminan; nunca se borra una + estadistica de una partida `'jugada'`, porque ya es un resultado + oficial. El ranking se recalcula con `UPDATE` solo a partir de las + partidas `'jugada'` reales. + +## Supuestos + +- El cliente no detallo la formula de puntos por partida; se asume que + cada jugador recibe una cantidad de puntos ya calculada externamente + (por kills, objetivos, etc.) y aqui solo se registra el resultado + final por jugador. +- Se asume que `ranking` es una tabla propia (no se calcula siempre al + vuelo) porque el cliente pidio poder "consultar rankings... desde la + base de datos", lo que sugiere un dato que se guarda y se corrige, + no solo se calcula en cada consulta. +- No se detallo si un jugador puede cambiar de equipo; se asume que no + para el alcance de este nivel. + +## Preguntas que responde la base de datos + +1. Que estadisticas existen, con que jugador y que partida. +2. Que partidas estan programadas (casos pendientes), jugadas o + canceladas. +3. Que jugador tiene mas actividad (mas partidas con estadisticas + registradas). +4. Como se ordena el ranking final, de mayor a menor puntaje. +5. Que equipos superan cierto puntaje total, para decidir quienes + avanzan a la siguiente fase. diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-078/ddl/schema.sql b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-078/ddl/schema.sql new file mode 100644 index 00000000..baf35c58 --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-078/ddl/schema.sql @@ -0,0 +1,55 @@ +PRAGMA foreign_keys = ON; + +-- Ejercicio 078: Torneo Esports +-- Modelo: equipos -> jugadores (1:N); equipos -> partidas (1:N, como +-- local y como visitante); partidas + jugadores -> estadisticas +-- (1:N cada una); equipos -> ranking (1:1). + +CREATE TABLE equipos ( + id_equipo INTEGER PRIMARY KEY AUTOINCREMENT, + nombre_equipo TEXT NOT NULL UNIQUE, + region TEXT NOT NULL +); + +CREATE TABLE jugadores ( + id_jugador INTEGER PRIMARY KEY AUTOINCREMENT, + nickname TEXT NOT NULL UNIQUE, + id_equipo INTEGER NOT NULL, + + FOREIGN KEY (id_equipo) REFERENCES equipos (id_equipo) +); + +CREATE TABLE partidas ( + id_partida INTEGER PRIMARY KEY AUTOINCREMENT, + id_equipo_local INTEGER NOT NULL, + id_equipo_visitante INTEGER NOT NULL, + fecha_partida TEXT NOT NULL, + estado TEXT NOT NULL DEFAULT 'programada' + CHECK (estado IN ('programada', 'jugada', 'cancelada')), + + FOREIGN KEY (id_equipo_local) REFERENCES equipos (id_equipo), + FOREIGN KEY (id_equipo_visitante) REFERENCES equipos (id_equipo) +); + +-- estadisticas: el UNIQUE compuesto impide que un jugador quede +-- cargado dos veces en la misma partida. +CREATE TABLE estadisticas ( + id_estadistica INTEGER PRIMARY KEY AUTOINCREMENT, + id_partida INTEGER NOT NULL, + id_jugador INTEGER NOT NULL, + puntos INTEGER NOT NULL DEFAULT 0 CHECK (puntos >= 0), + + FOREIGN KEY (id_partida) REFERENCES partidas (id_partida), + FOREIGN KEY (id_jugador) REFERENCES jugadores (id_jugador), + UNIQUE (id_partida, id_jugador) +); + +-- ranking: una sola fila por equipo (UNIQUE), que se corrige con +-- UPDATE a partir de las estadisticas de las partidas 'jugada'. +CREATE TABLE ranking ( + id_ranking INTEGER PRIMARY KEY AUTOINCREMENT, + id_equipo INTEGER NOT NULL UNIQUE, + puntos_totales INTEGER NOT NULL DEFAULT 0 CHECK (puntos_totales >= 0), + + FOREIGN KEY (id_equipo) REFERENCES equipos (id_equipo) +); diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-078/diagramas/.gitkeep b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-078/diagramas/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-078/diagramas/diagrama-er.svg b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-078/diagramas/diagrama-er.svg new file mode 100644 index 00000000..7cdb4dcd --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-078/diagramas/diagrama-er.svg @@ -0,0 +1,58 @@ + + + + + equipos + id_equipo PK + nombre_equipo UNIQUE + region + + + jugadores + id_jugador PK + nickname UNIQUE, id_equipo FK + + + ranking + id_ranking PK + id_equipo FK UNIQUE (1:1) + puntos_totales + + + partidas + id_partida PK + id_equipo_local/visitante FK + estado CHECK + + + estadisticas + id_estadistica PK + id_partida FK, id_jugador FK + puntos, UNIQUE(partida,jugador) + + + + 1:N + + + + 1:1 + + + + 1:N + + + + 1:N + + + + 1:N + diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-078/dml/inserts.sql b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-078/dml/inserts.sql new file mode 100644 index 00000000..09134451 --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-078/dml/inserts.sql @@ -0,0 +1,82 @@ +PRAGMA foreign_keys = ON; + +-- Ejercicio 078: Torneo Esports +-- Datos base: 3 equipos, 9 jugadores, 4 partidas (2 jugadas, 1 +-- jugada-por-error que se corrige despues, 1 programada = caso +-- pendiente), estadisticas de las partidas jugadas (incluye la +-- erronea) y el ranking inicial de los 3 equipos en 0. + +INSERT INTO equipos (nombre_equipo, region) VALUES + ('Titanes Cyber', 'Norte'), + ('Fenix Digital', 'Sur'), + ('Lobos Binarios', 'Centro'); + +INSERT INTO jugadores (nickname, id_equipo) VALUES + ('TitanBlaze', 1), + ('CyberFrost', 1), + ('IronCircuit', 1), + ('PhoenixArc', 2), + ('EmberCode', 2), + ('SolarFlux', 2), + ('WolfByte', 3), + ('ShadowLoop', 3), + ('NightHash', 3); + +-- Partida 1: Titanes Cyber (local) vs Fenix Digital (visitante), +-- jugada. +INSERT INTO partidas (id_equipo_local, id_equipo_visitante, fecha_partida, estado) VALUES + (1, 2, '2026-08-01', 'jugada'); + +-- Partida 2: Fenix Digital (local) vs Lobos Binarios (visitante), +-- jugada. +INSERT INTO partidas (id_equipo_local, id_equipo_visitante, fecha_partida, estado) VALUES + (2, 3, '2026-08-03', 'jugada'); + +-- Partida 3: Titanes Cyber vs Lobos Binarios. Se marco 'jugada' y se +-- cargaron estadisticas, pero el servidor de la plataforma fallo y el +-- resultado se anulo despues. Se corrige en dml/operaciones.sql. +INSERT INTO partidas (id_equipo_local, id_equipo_visitante, fecha_partida, estado) VALUES + (1, 3, '2026-08-05', 'jugada'); + +-- Partida 4: caso pendiente (programada), todavia no se juega. +INSERT INTO partidas (id_equipo_local, id_equipo_visitante, fecha_partida, estado) VALUES + (3, 1, '2026-08-08', 'programada'); + +-- Estadisticas de la partida 1. +INSERT INTO estadisticas (id_partida, id_jugador, puntos) VALUES + (1, 1, 10), + (1, 2, 8), + (1, 3, 6), + (1, 4, 7), + (1, 5, 5), + (1, 6, 4); + +-- Estadisticas de la partida 2. +INSERT INTO estadisticas (id_partida, id_jugador, puntos) VALUES + (2, 4, 9), + (2, 5, 6), + (2, 6, 5), + (2, 7, 12), + (2, 8, 10), + (2, 9, 8); + +-- Estadisticas cargadas por error para la partida 3, antes de saber +-- que el servidor habia fallado. Quedaran huerfanas cuando la partida +-- se marque 'cancelada' en dml/operaciones.sql, y se eliminan ahi +-- mismo. +INSERT INTO estadisticas (id_partida, id_jugador, puntos) VALUES + (3, 1, 5), + (3, 2, 3); + +-- Ranking inicial de los 3 equipos, en 0. Se recalcula con UPDATE en +-- dml/operaciones.sql a partir de las estadisticas de las partidas +-- 'jugada'. +INSERT INTO ranking (id_equipo, puntos_totales) VALUES + (1, 0), + (2, 0), + (3, 0); + +-- Caso comentado que debe fallar (queda comentado): cargar de nuevo a +-- TitanBlaze en la partida 1, exactamente el problema que este UNIQUE +-- esta disenado para evitar. +-- INSERT INTO estadisticas (id_partida, id_jugador, puntos) VALUES (1, 1, 10); diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-078/dml/operaciones.sql b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-078/dml/operaciones.sql new file mode 100644 index 00000000..c1bda84b --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-078/dml/operaciones.sql @@ -0,0 +1,37 @@ +PRAGMA foreign_keys = ON; + +-- Ejercicio 078: Torneo Esports +-- Operaciones de mantenimiento sobre los datos base. + +-- 1 UPDATE de estado: se confirma que el servidor fallo durante la +-- partida 3 y su resultado se anula. +UPDATE partidas +SET estado = 'cancelada' +WHERE id_partida = 3 AND estado = 'jugada'; + +-- 1 DELETE controlado (multiple): las estadisticas de la partida 3 +-- quedaron huerfanas apenas se marco 'cancelada'. Solo se borran +-- estadisticas de partidas 'cancelada'; una partida 'jugada' nunca +-- pierde sus estadisticas por este DELETE. +DELETE FROM estadisticas +WHERE id_partida IN ( + SELECT id_partida FROM partidas WHERE estado = 'cancelada' +); + +-- 3 UPDATE de recalculo: el ranking de cada equipo se corrige a +-- partir de las estadisticas de las partidas 'jugada' (la partida 3 +-- cancelada ya no aporta nada, porque sus estadisticas se eliminaron +-- arriba). +UPDATE ranking +SET puntos_totales = COALESCE(( + SELECT SUM(e.puntos) + FROM estadisticas e + JOIN jugadores j ON j.id_jugador = e.id_jugador + WHERE j.id_equipo = ranking.id_equipo +), 0); + +-- Caso que debe fallar / no ser recomendable (queda comentado): +-- borrar estadisticas de una partida que ya quedo 'jugada' (resultado +-- oficial del torneo, ya contado en el ranking). El DELETE de arriba +-- solo alcanza partidas 'cancelada' por diseno. +-- DELETE FROM estadisticas WHERE id_partida = 1; diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-078/dql/consultas.sql b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-078/dql/consultas.sql new file mode 100644 index 00000000..e93257c4 --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-078/dql/consultas.sql @@ -0,0 +1,48 @@ +.headers on +.mode column + +-- Ejercicio 078: Torneo Esports +-- Consultas que responden preguntas reales del cliente. + +-- 1. Que registros principales existen: todas las estadisticas con su +-- jugador y su partida. +SELECT e.id_estadistica, + j.nickname, + e.id_partida, + p.fecha_partida, + e.puntos +FROM estadisticas e +JOIN jugadores j ON j.id_jugador = e.id_jugador +JOIN partidas p ON p.id_partida = e.id_partida; + +-- 2. Que partidas estan programadas (casos pendientes), jugadas o +-- canceladas. +SELECT id_partida, fecha_partida, estado +FROM partidas +ORDER BY estado; + +-- 3. Que jugador tiene mas actividad (mas partidas con estadisticas +-- registradas). +SELECT j.nickname, COUNT(*) AS partidas_registradas +FROM jugadores j +JOIN estadisticas e ON e.id_jugador = j.id_jugador +GROUP BY j.id_jugador, j.nickname +ORDER BY partidas_registradas DESC, j.nickname; + +-- 4. Ranking final, de mayor a menor puntaje. +SELECT eq.nombre_equipo, r.puntos_totales +FROM ranking r +JOIN equipos eq ON eq.id_equipo = r.id_equipo +ORDER BY r.puntos_totales DESC; + +-- 5. Reporte para decision de negocio: equipos que superan un +-- puntaje total minimo, para decidir quienes avanzan a la siguiente +-- fase (GROUP BY + HAVING). +SELECT eq.nombre_equipo, + SUM(e.puntos) AS puntos_calculados +FROM estadisticas e +JOIN jugadores j ON j.id_jugador = e.id_jugador +JOIN equipos eq ON eq.id_equipo = j.id_equipo +GROUP BY eq.id_equipo, eq.nombre_equipo +HAVING SUM(e.puntos) >= 30 +ORDER BY puntos_calculados DESC; diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-078/evidencias/resultados.md b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-078/evidencias/resultados.md new file mode 100644 index 00000000..a90f384b --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-078/evidencias/resultados.md @@ -0,0 +1,72 @@ +# Evidencias - Solicitudes SQL - Ejercicio 078 (Torneo Esports) + +## 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-078.db < ddl/schema.sql +sqlite3 ejercicio-078.db < dml/inserts.sql +sqlite3 ejercicio-078.db < dml/operaciones.sql +sqlite3 ejercicio-078.db < dql/consultas.sql +``` + +## Resultados + +Datos base tras `dml/inserts.sql`: 3 equipos, 9 jugadores, 4 partidas +(3 marcadas `jugada` en algun momento, 1 `programada`), 14 filas de +estadisticas (incluye 2 cargadas por error para la partida que se +debia cancelar) y el ranking inicial en 0. + +**Caso comentado verificado:** + +- `INSERT INTO estadisticas (id_partida, id_jugador, ...) VALUES (1, 1, ...);` (repetir a TitanBlaze en la partida 1) → `UNIQUE constraint failed: estadisticas.id_partida, estadisticas.id_jugador`. + +**2. Partidas por estado (la partida 3 ya aparece `cancelada`, y la +partida 4 sigue `programada` — el caso pendiente que pidio el +cliente):** + +```text +id_partida | fecha_partida | estado +3 | 2026-08-05 | cancelada +1 | 2026-08-01 | jugada +2 | 2026-08-03 | jugada +4 | 2026-08-08 | programada +``` + +**4. Ranking final:** + +```text +nombre_equipo puntos_totales +Fenix Digital 36 +Lobos Binarios 30 +Titanes Cyber 24 +``` + +**5. Equipos con al menos 30 puntos (candidatos a la siguiente fase):** + +```text +nombre_equipo puntos_calculados +Fenix Digital 36 +Lobos Binarios 30 +``` + +## Operaciones de mantenimiento verificadas + +- `UPDATE partidas SET estado = 'cancelada' WHERE id_partida = 3 ...;` → la partida del 2026-08-05 se anulo despues de confirmarse la falla del servidor. +- **DELETE controlado (multiple)**: se eliminaron las 2 estadisticas que habian quedado huerfanas de la partida 3. Total de estadisticas: 14 -> 12. Ninguna estadistica de una partida `jugada` se toco. +- **UPDATE de recalculo del ranking**: los 3 equipos pasaron de `puntos_totales = 0` a los valores reales, calculados solo con las partidas `jugada` (1 y 2); la partida cancelada no aporto puntos porque sus estadisticas ya no existian cuando se ejecuto este `UPDATE`. + +## Aprendizaje + +El `UNIQUE (id_partida, id_jugador)` en `estadisticas` evita cargar al +mismo jugador dos veces en la misma partida. El `DELETE` controlado +(multiple, con una subconsulta sobre `estado = 'cancelada'`) limpia de +un solo golpe todas las estadisticas huerfanas sin arriesgar ninguna +partida `jugada`. El ranking, guardado como su propia tabla tal como +pidio el cliente, se mantiene siempre consistente porque su `UPDATE` +de recalculo solo cuenta partidas reales: la consulta 2 muestra +exactamente los "casos pendientes" (partidas `programada`) que el +cliente queria poder consultar directamente.