diff --git a/resoluciones/maria-montepeque/ejercicio-71/README.md b/resoluciones/maria-montepeque/ejercicio-71/README.md new file mode 100644 index 00000000..8ab9327d --- /dev/null +++ b/resoluciones/maria-montepeque/ejercicio-71/README.md @@ -0,0 +1,77 @@ +# Ejercicio 71: INSERT Nivel Basico + +**Nombre:** Maria Jose Montepeque +**Fecha:** 2026-08-24 + +## Tema central + +INSERT + +## Descripcion del problema + +Una clinica agenda citas relacionando pacientes con medicos. El +negocio necesita cargar medicos, pacientes y citas iniciales, tanto de +a uno como en lotes, y entender cuando conviene escribir todas las +columnas y cuando conviene dejar que `DEFAULT` complete lo que no se +indica. + +## Tablas y relaciones + +- `medicos`: catalogo de medicos. +- `pacientes`: catalogo de pacientes. +- `citas`: relaciona un paciente con un medico en una fecha, con un + estado. `pacientes` 1—N `citas`; `medicos` 1—N `citas`. + +## Uso de INSERT + +En `dml/inserts.sql`: + +1. `INSERT` de una sola fila: se registra el primer medico. +2. `INSERT` multiple (varias filas en una sola sentencia + `VALUES (...), (...)`): el resto de medicos, todos los pacientes y + dos citas con estado explicito. +3. `INSERT` multiple omitiendo a proposito la columna `estado`: las + citas 3 y 4 se insertan solo con `id_paciente`, `id_medico` y + `fecha_cita`, y quedan completas con `estado = 'programada'` gracias + al `DEFAULT` de la tabla. + +La consulta 5 en `dql/consultas.sql` confirma que esas dos citas no +quedaron con datos faltantes por haber omitido la columna. + +## Otras restricciones aplicadas + +- `PRIMARY KEY` autoincremental en las 3 tablas. +- `FOREIGN KEY`: `citas.id_paciente`, `citas.id_medico`. +- `NOT NULL` en todas las columnas obligatorias. +- `UNIQUE`: `medicos.nombre_medico`, `pacientes.telefono`. +- `CHECK`: `citas.estado IN (...)`. +- `DEFAULT` en `citas.estado`. +- `PRAGMA foreign_keys = ON;` activado al inicio de cada script. + +## Casos que fallan / no recomendables (comentados en `dml/inserts.sql`) + +Uno por cada restriccion, validado con Python (`sqlite3`): + +- Repetir `nombre_medico` -> `UNIQUE constraint failed`. +- Apuntar a un `id_medico` que no existe -> `FOREIGN KEY constraint failed`. +- Escribir un `estado` fuera de la lista permitida -> `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 medicos, 4 pacientes, 4 citas (2 atendidas, 2 + programadas). + +## Como ejecutar + +```bash +sqlite3 ejercicio-71.db < ddl/schema.sql +sqlite3 ejercicio-71.db < dml/inserts.sql +sqlite3 ejercicio-71.db < dql/consultas.sql +``` + +No suba archivos `.db`, `.sqlite` ni `.sqlite3`. diff --git a/resoluciones/maria-montepeque/ejercicio-71/ddl/schema.sql b/resoluciones/maria-montepeque/ejercicio-71/ddl/schema.sql new file mode 100644 index 00000000..0bf7399b --- /dev/null +++ b/resoluciones/maria-montepeque/ejercicio-71/ddl/schema.sql @@ -0,0 +1,29 @@ +PRAGMA foreign_keys = ON; + +-- Ejercicio 71: INSERT Nivel Basico +-- Tema central: INSERT +-- Contexto: agenda de citas medicas por fecha. + +CREATE TABLE medicos ( + id_medico INTEGER PRIMARY KEY AUTOINCREMENT, + nombre_medico TEXT NOT NULL UNIQUE, + especialidad TEXT NOT NULL +); + +CREATE TABLE pacientes ( + id_paciente INTEGER PRIMARY KEY AUTOINCREMENT, + nombre_paciente TEXT NOT NULL, + telefono TEXT NOT NULL UNIQUE +); + +CREATE TABLE citas ( + id_cita INTEGER PRIMARY KEY AUTOINCREMENT, + id_paciente INTEGER NOT NULL, + id_medico INTEGER NOT NULL, + fecha_cita TEXT NOT NULL, + estado TEXT NOT NULL DEFAULT 'programada' + CHECK (estado IN ('programada', 'atendida', 'cancelada')), + + FOREIGN KEY (id_paciente) REFERENCES pacientes (id_paciente), + FOREIGN KEY (id_medico) REFERENCES medicos (id_medico) +); diff --git a/resoluciones/maria-montepeque/ejercicio-71/dml/inserts.sql b/resoluciones/maria-montepeque/ejercicio-71/dml/inserts.sql new file mode 100644 index 00000000..9072d68e --- /dev/null +++ b/resoluciones/maria-montepeque/ejercicio-71/dml/inserts.sql @@ -0,0 +1,47 @@ +PRAGMA foreign_keys = ON; + +-- Ejercicio 71: INSERT Nivel Basico +-- Datos de prueba para validar el tema INSERT. + +-- 1. INSERT de una sola fila: se registra un medico. +INSERT INTO medicos (nombre_medico, especialidad) VALUES + ('Dra. Sofia Ramirez', 'Medicina General'); + +-- 2. INSERT multiple (varias filas en una sola sentencia): se +-- registran el resto de los medicos. +INSERT INTO medicos (nombre_medico, especialidad) VALUES + ('Dr. Carlos Perez', 'Pediatria'), + ('Dra. Marta Lopez', 'Traumatologia'); + +-- 3. INSERT multiple de pacientes, con todas las columnas explicitas. +INSERT INTO pacientes (nombre_paciente, telefono) VALUES + ('Manuel Estrada', '5555-7001'), + ('Alejandra Chinchilla', '5555-7002'), + ('Byron Xicay', '5555-7003'), + ('Cristina Barrios', '5555-7004'); + +-- 4. INSERT de citas con estado explicito (no depende de DEFAULT). +INSERT INTO citas (id_paciente, id_medico, fecha_cita, estado) VALUES + (1, 1, '2026-08-01 09:00', 'atendida'), + (2, 2, '2026-08-01 10:30', 'atendida'); + +-- 5. INSERT de citas SIN indicar estado: se omite a proposito para +-- que INSERT complete la columna con su DEFAULT ('programada'). Esto +-- demuestra que INSERT no exige escribir todas las columnas, solo las +-- que no tienen DEFAULT ni permiten NULL. +INSERT INTO citas (id_paciente, id_medico, fecha_cita) VALUES + (3, 1, '2026-08-02 08:00'), + (4, 3, '2026-08-03 11:00'); + +-- Casos comentados que deben fallar (no ser recomendables), dejar +-- comentados: + +-- 1) Registro repetido: nombre_medico ya existe, viola el UNIQUE. +-- INSERT INTO medicos (nombre_medico, especialidad) VALUES ('Dra. Sofia Ramirez', 'Cardiologia'); + +-- 2) Relacion invalida: id_medico = 99 no existe, viola el FOREIGN KEY. +-- INSERT INTO citas (id_paciente, id_medico, fecha_cita) VALUES (1, 99, '2026-08-04 09:00'); + +-- 3) Valor fuera de rango: estado con un valor que no esta en la +-- lista permitida, viola el CHECK. +-- INSERT INTO citas (id_paciente, id_medico, fecha_cita, estado) VALUES (2, 2, '2026-08-04 10:00', 'reagendada'); diff --git a/resoluciones/maria-montepeque/ejercicio-71/dql/consultas.sql b/resoluciones/maria-montepeque/ejercicio-71/dql/consultas.sql new file mode 100644 index 00000000..a56080df --- /dev/null +++ b/resoluciones/maria-montepeque/ejercicio-71/dql/consultas.sql @@ -0,0 +1,37 @@ +.headers on +.mode column + +-- Ejercicio 71: INSERT Nivel Basico +-- Consultas de validacion. + +-- 1. Mostrar todos los datos principales (citas con paciente y +-- medico). +SELECT c.id_cita, p.nombre_paciente, m.nombre_medico, + c.fecha_cita, c.estado +FROM citas c +JOIN pacientes p ON p.id_paciente = c.id_paciente +JOIN medicos m ON m.id_medico = c.id_medico; + +-- 2. Consulta con WHERE: citas ya atendidas. +SELECT id_cita, fecha_cita +FROM citas +WHERE estado = 'atendida'; + +-- 3. Consulta con ORDER BY: citas ordenadas por fecha. +SELECT id_cita, fecha_cita, estado +FROM citas +ORDER BY fecha_cita; + +-- 4. Conteo o resumen: total de citas por estado. +SELECT estado, COUNT(*) AS total +FROM citas +GROUP BY estado; + +-- 5. Validacion especifica de INSERT: las citas 3 y 4 se insertaron +-- SIN indicar estado, y aun asi quedaron completas gracias al +-- DEFAULT ('programada'). Esto confirma que el INSERT multiple con +-- columnas parciales cumplio su proposito: no quedo ninguna fila con +-- datos faltantes. +SELECT id_cita, fecha_cita, estado +FROM citas +WHERE id_cita IN (3, 4); diff --git a/resoluciones/maria-montepeque/ejercicio-71/evidencias/resultados.md b/resoluciones/maria-montepeque/ejercicio-71/evidencias/resultados.md new file mode 100644 index 00000000..52940524 --- /dev/null +++ b/resoluciones/maria-montepeque/ejercicio-71/evidencias/resultados.md @@ -0,0 +1,81 @@ +# Evidencias - Ejercicio 71 + +## 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-71.db < ddl/schema.sql +sqlite3 ejercicio-71.db < dml/inserts.sql +sqlite3 ejercicio-71.db < dql/consultas.sql +``` + +## Resultados + +Datos base tras `dml/inserts.sql`: 3 medicos, 4 pacientes, 4 citas. + +**Casos comentados verificados** (descomentados y ejecutados por +separado para confirmar que cada uno falla): + +- `INSERT INTO medicos (nombre_medico, ...) VALUES ('Dra. Sofia Ramirez', ...);` → `UNIQUE constraint failed: medicos.nombre_medico`. +- `INSERT INTO citas (id_paciente, id_medico, ...) VALUES (1, 99, ...);` → `FOREIGN KEY constraint failed`. +- `INSERT INTO citas (..., estado) VALUES (..., 'reagendada');` → `CHECK constraint failed: estado IN ('programada', 'atendida', 'cancelada')`. + +**1. Todas las citas, con JOIN a pacientes y medicos:** + +```text +id_cita | nombre_paciente | nombre_medico | fecha_cita | estado +1 | Manuel Estrada | Dra. Sofia Ramirez | 2026-08-01 09:00 | atendida +2 | Alejandra Chinchilla | Dr. Carlos Perez | 2026-08-01 10:30 | atendida +3 | Byron Xicay | Dra. Sofia Ramirez | 2026-08-02 08:00 | programada +4 | Cristina Barrios | Dra. Marta Lopez | 2026-08-03 11:00 | programada +``` + +**2. Citas ya atendidas:** + +```text +id_cita | fecha_cita +1 | 2026-08-01 09:00 +2 | 2026-08-01 10:30 +``` + +**3. Citas ordenadas por fecha:** ver tabla completa arriba, de +2026-08-01 a 2026-08-03. + +**4. Resumen: citas por estado:** + +```text +estado total +atendida 2 +programada 2 +``` + +**5. Validacion especifica de INSERT: citas 3 y 4, insertadas SIN +indicar estado:** + +```text +id_cita | fecha_cita | estado +3 | 2026-08-02 08:00 | programada +4 | 2026-08-03 11:00 | programada +``` + +Ambas quedaron completas con `estado = 'programada'` gracias al +`DEFAULT`, sin que el `INSERT` tuviera que escribir esa columna. + +## Aprendizaje + +`INSERT` no siempre necesita escribir todas las columnas de la tabla: +alcanza con las que no tienen `DEFAULT` ni permiten `NULL` (aqui, +`id_paciente`, `id_medico` y `fecha_cita`). Ademas, una sola sentencia +`INSERT ... VALUES` puede cargar varias filas a la vez (como los 3 +medicos o los 4 pacientes de este ejercicio), lo que evita repetir la +sentencia completa fila por fila. Las restricciones de la tabla +(`UNIQUE`, `FOREIGN KEY`, `CHECK`) actuan justo en el momento del +`INSERT`: si el dato no cumple la regla, la fila nunca llega a +guardarse, como se confirmo con los tres casos comentados. diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-071/README.md b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-071/README.md new file mode 100644 index 00000000..04bbaaa1 --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-071/README.md @@ -0,0 +1,86 @@ +# Solicitud SQL - Ejercicio 071: Battle Royale Ranking + +**Nombre:** Maria Jose Montepeque +**Fecha:** 2026-08-24 + +## Solicitud del cliente + +Una comunidad gamer registra partidas, kills, posiciones y ranking +semanal de battle royale. Hoy todo se maneja en hojas de calculo y +varias personas duplican datos sin darse cuenta. El cliente pidio +convertir esa operacion en una base de datos que permita consultar +datos, corregir estados, registrar movimientos y sacar reportes +utiles, no solo guardar texto. + +## Que entendi de la solicitud + +El problema central no es solo "guardar resultados", es evitar los +duplicados que hoy pasan en la hoja de calculo (el mismo jugador +cargado dos veces en la misma partida). 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 + +- `jugadores`: catalogo de participantes de la comunidad. +- `temporadas`: catalogo de periodos de competencia. +- `partidas`: tabla transaccional, cada una dentro de una temporada. +- `estadisticas`: detalle de cada partida (kills y posicion final por + jugador). Aqui es donde se ataca el problema del cliente: un + `UNIQUE (id_partida, id_jugador)` impide que un jugador quede + cargado dos veces en la misma partida. +- `ranking`: resumen de puntos por jugador y temporada, con su propio + `UNIQUE (id_temporada, id_jugador)`. Se corrige con `UPDATE` a + medida que se juegan mas partidas, nunca se reconstruye borrando + filas. + +## Como se relacionan + +`temporadas` 1:N `partidas`; `partidas` 1:N `estadisticas`; +`jugadores` 1:N `estadisticas`; `temporadas` 1:N `ranking`; +`jugadores` 1:N `ranking`. El diagrama esta en +[diagramas/diagrama-er.svg](diagramas/diagrama-er.svg). + +## Que datos de prueba use + +5 jugadores, 1 temporada, 5 partidas (3 `jugada`, 1 `programada` y 1 +que se juega y despues se anula por una caida de servidor), 16 filas +de estadisticas y el ranking inicial en 0, ademas de un `INSERT` +comentado que reproduce exactamente el problema del cliente (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 anulada pasa a `cancelada`), un `DELETE` controlado que +limpia las estadisticas huerfanas de esa partida (y solo de partidas +`cancelada`, nunca de una `jugada`), y un `UPDATE` que recalcula el +ranking de la temporada a partir de las estadisticas reales. + +## Que consultas responden al cliente + +En [dql/consultas.sql](dql/consultas.sql): que estadisticas existen +(JOIN jugador-partida), en que estado esta cada partida, que jugador +participo en mas partidas, el ranking final de la temporada ordenado +de mayor a menor, y un reporte con `GROUP BY` + `HAVING` de que +jugadores acumularon mas kills, para decidir a quien destacar como +MVP. + +## 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-071.db < ddl/schema.sql +sqlite3 ejercicio-071.db < dml/inserts.sql +sqlite3 ejercicio-071.db < dml/operaciones.sql +sqlite3 ejercicio-071.db < dql/consultas.sql +``` + +No suba archivos `.db`, `.sqlite`, `.sqlite3` ni `.dump`. diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-071/analisis/requerimiento.md b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-071/analisis/requerimiento.md new file mode 100644 index 00000000..bbb4a9e7 --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-071/analisis/requerimiento.md @@ -0,0 +1,80 @@ +# Analisis del requerimiento - Ejercicio 071 + +## Solicitud entendida + +Una comunidad gamer organiza partidas de battle royale y necesita +llevar el registro de kills, posiciones y ranking semanal por +temporada. Hoy todo se maneja en hojas de calculo y varias personas +duplican datos sin darse cuenta (por ejemplo, cargan dos veces el +resultado del mismo jugador en la misma partida). Se necesita una +base de datos que evite esos duplicados desde el diseno, permita +corregir estados, registrar movimientos y sacar reportes utiles como +el ranking final de una temporada. + +## Entidades detectadas + +| Entidad | Por que existe | Atributos importantes | +| --- | --- | --- | +| jugadores | Catalogo: cada participante de la comunidad | nickname (unico), region | +| temporadas | Catalogo: periodo de competencia con ranking propio | nombre_temporada (unico), fecha_inicio, fecha_fin | +| partidas | Tabla transaccional: cada partida jugada dentro de una temporada | fecha_partida, mapa, estado | +| estadisticas | Detalle de cada partida: kills y posicion final de cada jugador que participo | kills, posicion_final | +| ranking | Resumen por temporada: puntos totales acumulados de cada jugador | puntos_totales | + +## Relaciones detectadas + +| Relacion | Tipo | Explicacion | +| --- | --- | --- | +| temporadas -> partidas | 1:N | Una temporada agrupa varias partidas. | +| 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 a lo largo del tiempo. | +| temporadas -> ranking | 1:N | Una temporada tiene un ranking con una fila por jugador. | +| jugadores -> ranking | 1:N | Un jugador aparece en el ranking de cada temporada en la que jugo. | + +## Reglas de negocio + +- Regla 1 (el problema central del cliente: datos duplicados): un + jugador no puede tener mas de una fila de estadisticas en la misma + partida (`UNIQUE (id_partida, id_jugador)`). Esto es justo lo que + hoy falla en la hoja de calculo. +- Regla 2: un jugador solo puede tener una fila de ranking por + temporada (`UNIQUE (id_temporada, id_jugador)`). +- Regla 3: una partida nace `'programada'` y solo puede avanzar a + `'jugada'` o `'cancelada'` (`CHECK`). +- Regla 4: `kills` nunca puede ser negativo y `posicion_final` siempre + debe ser 1 o mayor (`CHECK`). +- Regla 5: si una partida se cancela despues de haber cargado + estadisticas por error, esas filas de estadisticas se eliminan + porque no deben contar para el ranking; nunca se borra una + estadistica de una partida que ya quedo `'jugada'` (eso alteraria un + resultado ya oficial). +- Regla 6: los puntos del ranking se calculan a partir de las + estadisticas de las partidas `'jugada'` de la temporada: 1 punto por + kill, mas un bono por posicion final (10 puntos por el primer lugar, + 5 puntos del segundo al quinto lugar, 0 puntos del sexto lugar en + adelante). El ranking se corrige con `UPDATE`, nunca se recalcula + borrando y reinsertando filas. + +## Supuestos + +- El cliente no detallo la formula exacta de puntos; se asume + 1 punto por kill + bono por posicion (10 para el 1er lugar, 5 para + el 2do-5to lugar) por ser un esquema comun en battle royale y + suficiente para demostrar el modelo. +- No se detallo si un jugador puede cambiar de region; se asume que + `region` es un dato descriptivo simple, no una llave de otra tabla, + para el alcance de este nivel. +- Se asume que el ranking se guarda como tabla propia (no se calcula + siempre al vuelo) porque el cliente pidio poder "corregir estados" y + "sacar reportes", lo que sugiere que el ranking es un dato que se + actualiza y se consulta seguido. + +## Preguntas que responde la base de datos + +1. Que estadisticas existen, con que jugador y en que partida. +2. Que partidas estan programadas, jugadas o canceladas. +3. Que jugador participo en mas partidas (ranking de actividad). +4. Como se ordena el ranking final de la temporada, de mayor a menor + puntaje. +5. Que jugadores acumularon mas kills en la temporada, para decidir a + quien destacar como MVP. diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-071/ddl/schema.sql b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-071/ddl/schema.sql new file mode 100644 index 00000000..c5e8c287 --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-071/ddl/schema.sql @@ -0,0 +1,64 @@ +PRAGMA foreign_keys = ON; + +-- Ejercicio 071: Battle Royale Ranking +-- Modelo: temporadas -> partidas (1:N); partidas + jugadores -> +-- estadisticas (1:N cada una); temporadas + jugadores -> ranking +-- (1:N cada una). El UNIQUE compuesto en estadisticas ataca +-- directamente el problema del cliente: datos duplicados en la hoja +-- de calculo. + +CREATE TABLE jugadores ( + id_jugador INTEGER PRIMARY KEY AUTOINCREMENT, + nickname TEXT NOT NULL UNIQUE, + region TEXT NOT NULL +); + +CREATE TABLE temporadas ( + id_temporada INTEGER PRIMARY KEY AUTOINCREMENT, + nombre_temporada TEXT NOT NULL UNIQUE, + fecha_inicio TEXT NOT NULL, + fecha_fin TEXT NOT NULL, + + CHECK (fecha_fin > fecha_inicio) +); + +CREATE TABLE partidas ( + id_partida INTEGER PRIMARY KEY AUTOINCREMENT, + id_temporada INTEGER NOT NULL, + fecha_partida TEXT NOT NULL, + mapa TEXT NOT NULL, + estado TEXT NOT NULL DEFAULT 'programada' + CHECK (estado IN ('programada', 'jugada', 'cancelada')), + + FOREIGN KEY (id_temporada) REFERENCES temporadas (id_temporada) +); + +-- estadisticas: detalle de cada partida. El UNIQUE compuesto impide +-- que el mismo jugador quede cargado dos veces en la misma partida, +-- que es justo el problema que el cliente describio de la hoja de +-- calculo. +CREATE TABLE estadisticas ( + id_estadistica INTEGER PRIMARY KEY AUTOINCREMENT, + id_partida INTEGER NOT NULL, + id_jugador INTEGER NOT NULL, + kills INTEGER NOT NULL DEFAULT 0 CHECK (kills >= 0), + posicion_final INTEGER NOT NULL CHECK (posicion_final >= 1), + + FOREIGN KEY (id_partida) REFERENCES partidas (id_partida), + FOREIGN KEY (id_jugador) REFERENCES jugadores (id_jugador), + UNIQUE (id_partida, id_jugador) +); + +-- ranking: resumen por temporada. Tambien lleva un UNIQUE compuesto: +-- un jugador solo tiene una fila de ranking por temporada, que se +-- corrige con UPDATE a medida que se juegan mas partidas. +CREATE TABLE ranking ( + id_ranking INTEGER PRIMARY KEY AUTOINCREMENT, + id_temporada INTEGER NOT NULL, + id_jugador INTEGER NOT NULL, + puntos_totales INTEGER NOT NULL DEFAULT 0 CHECK (puntos_totales >= 0), + + FOREIGN KEY (id_temporada) REFERENCES temporadas (id_temporada), + FOREIGN KEY (id_jugador) REFERENCES jugadores (id_jugador), + UNIQUE (id_temporada, id_jugador) +); diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-071/diagramas/.gitkeep b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-071/diagramas/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-071/diagramas/diagrama-er.svg b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-071/diagramas/diagrama-er.svg new file mode 100644 index 00000000..fc1719c5 --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-071/diagramas/diagrama-er.svg @@ -0,0 +1,61 @@ + + + + + jugadores + id_jugador PK + nickname UNIQUE + region + + + temporadas + id_temporada PK + nombre_temporada UNIQUE + fecha_inicio, fecha_fin + + + partidas + id_partida PK + id_temporada FK + fecha_partida, mapa + estado CHECK + + + estadisticas + id_estadistica PK + id_partida FK, id_jugador FK + kills CHECK >= 0 + UNIQUE (id_partida, id_jugador) + + + ranking + id_ranking PK + id_temporada FK, id_jugador FK + UNIQUE (id_temporada, id_jugador) + + + + 1:N + + + + 1:N + + + + 1:N + + + + 1:N + + + + 1:N + diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-071/dml/inserts.sql b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-071/dml/inserts.sql new file mode 100644 index 00000000..ddbb2576 --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-071/dml/inserts.sql @@ -0,0 +1,75 @@ +PRAGMA foreign_keys = ON; + +-- Ejercicio 071: Battle Royale Ranking +-- Datos base: 5 jugadores, 1 temporada, 5 partidas (3 jugadas, +-- 1 programada, 1 cancelada), estadisticas de las partidas jugadas y +-- de la cancelada (para demostrar el DELETE controlado), y el +-- ranking inicial de la temporada en 0. + +INSERT INTO jugadores (nickname, region) VALUES + ('ShadowKill', 'Norte'), + ('NightFury', 'Sur'), + ('QuickScope', 'Centro'), + ('IronWolf', 'Oeste'), + ('PixelQueen', 'Centro'); + +INSERT INTO temporadas (nombre_temporada, fecha_inicio, fecha_fin) VALUES + ('Temporada 1 - Verano 2026', '2026-07-01', '2026-08-31'); + +INSERT INTO partidas (id_temporada, fecha_partida, mapa, estado) VALUES + (1, '2026-08-01', 'Isla Tormenta', 'jugada'), + (1, '2026-08-03', 'Desierto Rojo', 'jugada'), + (1, '2026-08-05', 'Bosque Nocturno', 'jugada'), + (1, '2026-08-08', 'Isla Tormenta', 'programada'); + +-- Partida 5: se cargo como 'jugada' con sus estadisticas, pero el +-- servidor se cayo a la mitad de la partida y el resultado se anulo +-- despues. Se corrige el estado con UPDATE en dml/operaciones.sql. +INSERT INTO partidas (id_temporada, fecha_partida, mapa, estado) VALUES + (1, '2026-08-02', 'Desierto Rojo', 'jugada'); + +-- Estadisticas de la partida 1 (Isla Tormenta, jugada por los 5). +INSERT INTO estadisticas (id_partida, id_jugador, kills, posicion_final) VALUES + (1, 1, 5, 1), + (1, 2, 3, 2), + (1, 3, 1, 4), + (1, 4, 0, 8), + (1, 5, 2, 3); + +-- Estadisticas de la partida 2 (Desierto Rojo, PixelQueen no jugo). +INSERT INTO estadisticas (id_partida, id_jugador, kills, posicion_final) VALUES + (2, 1, 2, 3), + (2, 2, 6, 1), + (2, 3, 4, 2), + (2, 4, 1, 6); + +-- Estadisticas de la partida 3 (Bosque Nocturno, jugada por los 5). +INSERT INTO estadisticas (id_partida, id_jugador, kills, posicion_final) VALUES + (3, 1, 1, 5), + (3, 2, 2, 4), + (3, 3, 7, 1), + (3, 4, 3, 2), + (3, 5, 0, 10); + +-- Estadisticas de la partida 5, cargadas antes de saber que el +-- servidor se habia caido. Quedaran huerfanas cuando la partida se +-- marque 'cancelada' en dml/operaciones.sql, y se eliminan ahi mismo. +INSERT INTO estadisticas (id_partida, id_jugador, kills, posicion_final) VALUES + (5, 1, 4, 2), + (5, 2, 1, 6); + +-- Ranking inicial de la temporada: una fila por jugador, en 0. Se +-- recalcula con UPDATE en dml/operaciones.sql a partir de las +-- estadisticas de las partidas 'jugada'. +INSERT INTO ranking (id_temporada, id_jugador, puntos_totales) VALUES + (1, 1, 0), + (1, 2, 0), + (1, 3, 0), + (1, 4, 0), + (1, 5, 0); + +-- Caso comentado que debe fallar (queda comentado): cargar de nuevo a +-- ShadowKill en la partida 1 es justo el problema que describio el +-- cliente (dato duplicado en la hoja de calculo); el UNIQUE +-- (id_partida, id_jugador) lo bloquea. +-- INSERT INTO estadisticas (id_partida, id_jugador, kills, posicion_final) VALUES (1, 1, 5, 1); diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-071/dml/operaciones.sql b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-071/dml/operaciones.sql new file mode 100644 index 00000000..6d5aec52 --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-071/dml/operaciones.sql @@ -0,0 +1,49 @@ +PRAGMA foreign_keys = ON; + +-- Ejercicio 071: Battle Royale Ranking +-- Operaciones de mantenimiento sobre los datos base. + +-- 1 UPDATE de estado: se confirma que la partida 5 se cayo a la +-- mitad y su resultado se anula. +UPDATE partidas +SET estado = 'cancelada' +WHERE id_partida = 5 AND estado = 'jugada'; + +-- 1 DELETE controlado: las estadisticas de la partida 5 quedaron +-- huerfanas apenas se marco 'cancelada' (no deben contar para el +-- ranking). 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' +); + +-- 1 UPDATE de recalculo: el ranking de la temporada se actualiza a +-- partir de las estadisticas de las partidas 'jugada' (1 punto por +-- kill, mas bono de 10 puntos para el 1er lugar y 5 puntos del 2do al +-- 5to lugar). Se corrige el ranking existente, no se borra ni se +-- vuelve a insertar. +UPDATE ranking +SET puntos_totales = COALESCE(( + SELECT SUM( + e.kills + + CASE + WHEN e.posicion_final = 1 THEN 10 + WHEN e.posicion_final <= 5 THEN 5 + ELSE 0 + END + ) + FROM estadisticas e + JOIN partidas p ON p.id_partida = e.id_partida + WHERE e.id_jugador = ranking.id_jugador + AND p.id_temporada = ranking.id_temporada + AND p.estado = 'jugada' +), 0) +WHERE id_temporada = 1; + +-- Caso que debe fallar / no ser recomendable (queda comentado): +-- borrar estadisticas de una partida que ya quedo 'jugada' (resultado +-- oficial, ya contado en el ranking). El DELETE de arriba solo alcanza +-- partidas 'cancelada' por diseno; esto seria un error de negocio, no +-- solo tecnico. +-- DELETE FROM estadisticas WHERE id_partida = 1; diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-071/dql/consultas.sql b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-071/dql/consultas.sql new file mode 100644 index 00000000..f378d3d9 --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-071/dql/consultas.sql @@ -0,0 +1,49 @@ +.headers on +.mode column + +-- Ejercicio 071: Battle Royale Ranking +-- 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, + p.mapa, + p.fecha_partida, + e.kills, + e.posicion_final +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, jugadas o canceladas. +SELECT id_partida, mapa, fecha_partida, estado +FROM partidas +ORDER BY estado; + +-- 3. Que jugador participo en mas partidas (ranking de actividad). +SELECT j.nickname, COUNT(*) AS total_partidas +FROM jugadores j +JOIN estadisticas e ON e.id_jugador = j.id_jugador +GROUP BY j.id_jugador, j.nickname +ORDER BY total_partidas DESC, j.nickname; + +-- 4. Ranking final de la temporada, de mayor a menor puntaje. +SELECT j.nickname, r.puntos_totales +FROM ranking r +JOIN jugadores j ON j.id_jugador = r.id_jugador +WHERE r.id_temporada = 1 +ORDER BY r.puntos_totales DESC; + +-- 5. Reporte para decision de negocio: jugadores con mas kills +-- acumulados en partidas 'jugada' de la temporada, para decidir a +-- quien destacar como MVP (GROUP BY + HAVING). +SELECT j.nickname, + SUM(e.kills) AS kills_totales +FROM estadisticas e +JOIN jugadores j ON j.id_jugador = e.id_jugador +JOIN partidas p ON p.id_partida = e.id_partida +WHERE p.estado = 'jugada' +GROUP BY j.id_jugador, j.nickname +HAVING SUM(e.kills) >= 8 +ORDER BY kills_totales DESC; diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-071/evidencias/resultados.md b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-071/evidencias/resultados.md new file mode 100644 index 00000000..c9aba66b --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-071/evidencias/resultados.md @@ -0,0 +1,106 @@ +# Evidencias - Solicitudes SQL - Ejercicio 071 (Battle Royale Ranking) + +## 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-071.db < ddl/schema.sql +sqlite3 ejercicio-071.db < dml/inserts.sql +sqlite3 ejercicio-071.db < dml/operaciones.sql +sqlite3 ejercicio-071.db < dql/consultas.sql +``` + +## Resultados + +Datos base tras `dml/inserts.sql`: 5 jugadores, 1 temporada, 5 +partidas (3 jugadas, 1 programada, 1 jugada-por-error que se corrige +despues), 16 filas de estadisticas y 5 filas de ranking en 0. + +**Caso comentado verificado** (el problema central del cliente): + +- `INSERT INTO estadisticas (id_partida, id_jugador, ...) VALUES (1, 1, ...);` (repetir a ShadowKill en la partida 1) → `UNIQUE constraint failed: estadisticas.id_partida, estadisticas.id_jugador`. + +**1. Todas las estadisticas, con JOIN a jugador y partida (14 filas +tras el DELETE de la partida cancelada):** + +```text +id_estadistica | nickname | mapa | fecha_partida | kills | posicion_final +1 | ShadowKill | Isla Tormenta | 2026-08-01 | 5 | 1 +2 | NightFury | Isla Tormenta | 2026-08-01 | 3 | 2 +3 | QuickScope | Isla Tormenta | 2026-08-01 | 1 | 4 +4 | IronWolf | Isla Tormenta | 2026-08-01 | 0 | 8 +5 | PixelQueen | Isla Tormenta | 2026-08-01 | 2 | 3 +6 | ShadowKill | Desierto Rojo | 2026-08-03 | 2 | 3 +7 | NightFury | Desierto Rojo | 2026-08-03 | 6 | 1 +8 | QuickScope | Desierto Rojo | 2026-08-03 | 4 | 2 +9 | IronWolf | Desierto Rojo | 2026-08-03 | 1 | 6 +10 | ShadowKill | Bosque Nocturno | 2026-08-05 | 1 | 5 +11 | NightFury | Bosque Nocturno | 2026-08-05 | 2 | 4 +12 | QuickScope | Bosque Nocturno | 2026-08-05 | 7 | 1 +13 | IronWolf | Bosque Nocturno | 2026-08-05 | 3 | 2 +14 | PixelQueen | Bosque Nocturno | 2026-08-05 | 0 | 10 +``` + +**2. Partidas por estado:** + +```text +id_partida | mapa | fecha_partida | estado +5 | Desierto Rojo | 2026-08-02 | cancelada +1 | Isla Tormenta | 2026-08-01 | jugada +2 | Desierto Rojo | 2026-08-03 | jugada +3 | Bosque Nocturno | 2026-08-05 | jugada +4 | Isla Tormenta | 2026-08-08 | programada +``` + +**3. Jugador con mas partidas jugadas:** + +```text +nickname | total_partidas +IronWolf | 3 +NightFury | 3 +QuickScope | 3 +ShadowKill | 3 +PixelQueen | 2 +``` + +**4. Ranking final de la temporada:** + +```text +nickname | puntos_totales +QuickScope | 32 +NightFury | 31 +ShadowKill | 28 +IronWolf | 9 +PixelQueen | 7 +``` + +**5. Jugadores con mas kills acumulados (candidatos a MVP, minimo 8 +kills):** + +```text +nickname | kills_totales +QuickScope | 12 +NightFury | 11 +ShadowKill | 8 +``` + +## Operaciones de mantenimiento verificadas + +- `UPDATE partidas SET estado = 'cancelada' WHERE id_partida = 5 ...;` → la partida de Desierto Rojo del 2026-08-02 se anulo despues de la caida del servidor. +- **DELETE controlado**: se eliminaron las 2 estadisticas huerfanas de la partida 5 apenas quedo `cancelada`. Total de estadisticas: 16 -> 14. Ninguna estadistica de una partida `jugada` se toco. +- **UPDATE de recalculo del ranking**: los 5 jugadores pasaron de `puntos_totales = 0` a los valores reales calculados solo con las partidas `jugada` (1, 2 y 3); la partida cancelada no aporto puntos porque sus estadisticas ya no existen quando se ejecuto este `UPDATE`. + +## Aprendizaje + +El `UNIQUE (id_partida, id_jugador)` en `estadisticas` resuelve +directamente el problema que describio el cliente: ya no es posible +cargar dos veces el resultado del mismo jugador en la misma partida, +sin importar cuantas personas editen la hoja de calculo original. El +`DELETE` controlado solo alcanza estadisticas de partidas `cancelada`, +nunca de una partida `jugada` cuyo resultado ya se conto en el +ranking, y el `UPDATE` de recalculo demuestra que el ranking es un +dato derivado que se corrige, no se reconstruye borrando e insertando +filas nuevas.