From c3d5c81780e619fef2c7072f5bec4843ea11034c Mon Sep 17 00:00:00 2001 From: MariaJoseMontepequeZet Date: Tue, 25 Aug 2026 14:18:45 -0600 Subject: [PATCH 1/2] feat(sql): resolver ejercicio 90 --- .../maria-montepeque/ejercicio-90/README.md | 72 ++++++++++++++++++ .../ejercicio-90/ddl/schema.sql | 31 ++++++++ .../ejercicio-90/dml/inserts.sql | 28 +++++++ .../ejercicio-90/dql/consultas.sql | 51 +++++++++++++ .../ejercicio-90/evidencias/resultados.md | 73 +++++++++++++++++++ 5 files changed, 255 insertions(+) create mode 100644 resoluciones/maria-montepeque/ejercicio-90/README.md create mode 100644 resoluciones/maria-montepeque/ejercicio-90/ddl/schema.sql create mode 100644 resoluciones/maria-montepeque/ejercicio-90/dml/inserts.sql create mode 100644 resoluciones/maria-montepeque/ejercicio-90/dql/consultas.sql create mode 100644 resoluciones/maria-montepeque/ejercicio-90/evidencias/resultados.md diff --git a/resoluciones/maria-montepeque/ejercicio-90/README.md b/resoluciones/maria-montepeque/ejercicio-90/README.md new file mode 100644 index 00000000..abf26cd1 --- /dev/null +++ b/resoluciones/maria-montepeque/ejercicio-90/README.md @@ -0,0 +1,72 @@ +# Ejercicio 90: GROUP BY Nivel Intermedio + +**Nombre:** Maria Jose Montepeque +**Fecha:** 2026-08-25 + +## Tema central + +GROUP BY + +## Descripcion del problema + +Un torneo de videojuegos necesita administrar las partidas y los +puntajes de cada equipo, para saber cuantas partidas juega cada uno y +que equipos tienen mejor rendimiento promedio. + +## Tablas y relaciones + +- `equipos`: catalogo de equipos. +- `jugadores`: catalogo de jugadores, cada uno pertenece a un equipo. +- `partidas`: tabla principal, con `puntaje` por partida jugada. + `equipos` 1—N `jugadores`; `equipos` 1—N `partidas`. + +## Uso de GROUP BY + +En `dql/consultas.sql`: + +1. Conteo simple: `GROUP BY id_equipo` con `COUNT(*)`, para saber + cuantas partidas jugo cada equipo. +2. Suma y promedio con `HAVING`: la consulta 5 agrupa las partidas por + equipo y calcula `SUM(puntaje)` y `AVG(puntaje)` por grupo, y usa + `HAVING AVG(...) > 70` para quedarse solo con los equipos cuyo + promedio supera los 70 puntos. + +## Otras restricciones aplicadas + +- `PRIMARY KEY` autoincremental en las 3 tablas. +- `FOREIGN KEY`: `jugadores.id_equipo`, `partidas.id_equipo`. +- `NOT NULL` en todas las columnas obligatorias. +- `UNIQUE`: `equipos.nombre_equipo`, `jugadores.gamer_tag`. +- `CHECK`: `partidas.puntaje >= 0`, `partidas.resultado IN (...)`. +- `DEFAULT` en `partidas.resultado`. +- `PRAGMA foreign_keys = ON;` activado al inicio del script. + +## Caso que falla / no recomendable (comentado en `dql/consultas.sql`) + +Agrupar por equipo (`GROUP BY e.id_equipo`) pero mostrar +`j.nombre_jugador` sin agregarlo ni incluirlo en el `GROUP BY`. +SQLite lo permite (a diferencia de MySQL en modo estricto +`ONLY_FULL_GROUP_BY`, donde fallaria), pero el valor de +`nombre_jugador` que devuelve es arbitrario: se verifico con Python +(`sqlite3`) que para "Dragones Digitales" (2 jugadores, 3 partidas) +el resultado muestra "Alejandra Chinchilla" y un `COUNT(*)` de 6 en +vez de 3, porque el `JOIN` con `jugadores` duplica cada partida por +cada jugador del equipo antes de agrupar. La version correcta (sin el +`JOIN` a `jugadores`) es la de la consulta 4. + +## 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). + +## Como ejecutar + +```bash +sqlite3 ejercicio-90.db < ddl/schema.sql +sqlite3 ejercicio-90.db < dml/inserts.sql +sqlite3 ejercicio-90.db < dql/consultas.sql +``` + +No suba archivos `.db`, `.sqlite` ni `.sqlite3`. diff --git a/resoluciones/maria-montepeque/ejercicio-90/ddl/schema.sql b/resoluciones/maria-montepeque/ejercicio-90/ddl/schema.sql new file mode 100644 index 00000000..7d593d50 --- /dev/null +++ b/resoluciones/maria-montepeque/ejercicio-90/ddl/schema.sql @@ -0,0 +1,31 @@ +PRAGMA foreign_keys = ON; + +-- Ejercicio 90: GROUP BY Nivel Intermedio +-- Tema central: GROUP BY +-- Contexto: torneo de videojuegos, equipos y sus partidas. + +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, + id_equipo INTEGER NOT NULL, + nombre_jugador TEXT NOT NULL, + gamer_tag TEXT NOT NULL UNIQUE, + + FOREIGN KEY (id_equipo) REFERENCES equipos (id_equipo) +); + +CREATE TABLE partidas ( + id_partida INTEGER PRIMARY KEY AUTOINCREMENT, + id_equipo INTEGER NOT NULL, + fecha_partida TEXT NOT NULL, + puntaje INTEGER NOT NULL CHECK (puntaje >= 0), + resultado TEXT NOT NULL DEFAULT 'pendiente' + CHECK (resultado IN ('victoria', 'derrota', 'empate', 'pendiente')), + + FOREIGN KEY (id_equipo) REFERENCES equipos (id_equipo) +); diff --git a/resoluciones/maria-montepeque/ejercicio-90/dml/inserts.sql b/resoluciones/maria-montepeque/ejercicio-90/dml/inserts.sql new file mode 100644 index 00000000..524dc644 --- /dev/null +++ b/resoluciones/maria-montepeque/ejercicio-90/dml/inserts.sql @@ -0,0 +1,28 @@ +PRAGMA foreign_keys = ON; + +-- Ejercicio 90: GROUP BY Nivel Intermedio +-- Datos de prueba: 3 equipos, 4 jugadores, 7 partidas. + +INSERT INTO equipos (nombre_equipo, region) VALUES + ('Dragones Digitales', 'GT-Central'), + ('Halcones Nocturnos', 'GT-Occidente'), + ('Fenix Cibernetico', 'GT-Oriente'); + +INSERT INTO jugadores (id_equipo, nombre_jugador, gamer_tag) VALUES + (1, 'Manuel Estrada', 'M-Blaze'), + (1, 'Alejandra Chinchilla', 'AleShadow'), + (2, 'Byron Xicay', 'ByroWolf'), + (3, 'Cristina Barrios', 'CrisFrost'); + +INSERT INTO partidas (id_equipo, fecha_partida, puntaje, resultado) VALUES + (1, '2026-08-10', 85, 'victoria'), + (1, '2026-08-12', 60, 'derrota'), + (1, '2026-08-14', 90, 'victoria'), + (2, '2026-08-10', 70, 'victoria'), + (2, '2026-08-13', 55, 'derrota'), + (3, '2026-08-11', 40, 'derrota'), + (3, '2026-08-15', 65, 'empate'); + +-- Caso comentado que no se debe hacer, dejar comentado: registrar una +-- partida con puntaje negativo. El CHECK (puntaje >= 0) lo rechaza. +-- INSERT INTO partidas (id_equipo, fecha_partida, puntaje, resultado) VALUES (1, '2026-08-16', -10, 'derrota'); diff --git a/resoluciones/maria-montepeque/ejercicio-90/dql/consultas.sql b/resoluciones/maria-montepeque/ejercicio-90/dql/consultas.sql new file mode 100644 index 00000000..fa5682b7 --- /dev/null +++ b/resoluciones/maria-montepeque/ejercicio-90/dql/consultas.sql @@ -0,0 +1,51 @@ +.headers on +.mode column + +-- Ejercicio 90: GROUP BY Nivel Intermedio +-- Consultas de validacion. + +-- 1. Mostrar todos los datos principales. +SELECT p.id_partida, e.nombre_equipo, p.fecha_partida, p.puntaje, p.resultado +FROM partidas p +JOIN equipos e ON e.id_equipo = p.id_equipo; + +-- 2. Consulta con WHERE: solo las partidas ganadas. +SELECT id_partida, id_equipo, fecha_partida, puntaje +FROM partidas +WHERE resultado = 'victoria'; + +-- 3. Consulta con ORDER BY: partidas ordenadas por fecha. +SELECT id_partida, fecha_partida, resultado +FROM partidas +ORDER BY fecha_partida; + +-- 4. Conteo o resumen: total de partidas jugadas por equipo (GROUP BY simple). +SELECT id_equipo, COUNT(*) AS total_partidas +FROM partidas +GROUP BY id_equipo; + +-- 5. Validacion especifica de GROUP BY: por cada equipo, suma y +-- promedio del puntaje de sus partidas, filtrando con HAVING solo a +-- los equipos cuyo promedio supera los 70 puntos. HAVING filtra +-- despues de agrupar (a diferencia de WHERE, que filtraria filas +-- individuales antes de que existan los grupos). +SELECT e.nombre_equipo, + COUNT(*) AS total_partidas, + SUM(p.puntaje) AS puntaje_total, + ROUND(AVG(p.puntaje), 2) AS promedio_puntaje +FROM partidas p +JOIN equipos e ON e.id_equipo = p.id_equipo +GROUP BY e.id_equipo, e.nombre_equipo +HAVING AVG(p.puntaje) > 70; + +-- Caso comentado que no es recomendable, dejar comentado: agrupar por +-- equipo pero mostrar nombre_jugador sin agregarlo ni incluirlo en el +-- GROUP BY. SQLite lo permite (a diferencia de MySQL en modo +-- estricto ONLY_FULL_GROUP_BY, donde fallaria), pero el valor de +-- nombre_jugador que devuelve es arbitrario: no representa a todo el +-- grupo, porque un equipo puede tener varios jugadores. +-- SELECT e.nombre_equipo, j.nombre_jugador, COUNT(*) +-- FROM partidas p +-- JOIN equipos e ON e.id_equipo = p.id_equipo +-- JOIN jugadores j ON j.id_equipo = e.id_equipo +-- GROUP BY e.id_equipo; diff --git a/resoluciones/maria-montepeque/ejercicio-90/evidencias/resultados.md b/resoluciones/maria-montepeque/ejercicio-90/evidencias/resultados.md new file mode 100644 index 00000000..a1077e53 --- /dev/null +++ b/resoluciones/maria-montepeque/ejercicio-90/evidencias/resultados.md @@ -0,0 +1,73 @@ +# Evidencias - Ejercicio 90 + +## Tema + +GROUP BY + +## 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-90.db < ddl/schema.sql +sqlite3 ejercicio-90.db < dml/inserts.sql +sqlite3 ejercicio-90.db < dql/consultas.sql +``` + +## Resultados + +**4. Total de partidas por equipo:** + +```text +id_equipo total_partidas +1 3 +2 2 +3 2 +``` + +**5. Equipos con promedio de puntaje mayor a 70:** + +```text +nombre_equipo total_partidas puntaje_total promedio_puntaje +Dragones Digitales 3 235 78.33 +``` + +Verificacion manual: Halcones Nocturnos (equipo 2) tiene 2 partidas +de 70+55=125 puntos, promedio 62.5; Fenix Cibernetico (equipo 3) tiene +40+65=105 puntos, promedio 52.5. Ambos por debajo del umbral de 70 y +por eso no aparecen en el resultado. + +**Caso comentado verificado (CHECK):** + +- `INSERT INTO partidas (..., puntaje, ...) VALUES (1, '2026-08-16', -10, 'derrota');` → `CHECK constraint failed: puntaje >= 0`. + +**Caso comentado verificado (GROUP BY no recomendable):** + +```text +nombre_equipo nombre_jugador COUNT(*) +Dragones Digitales Alejandra Chinchilla 6 +Halcones Nocturnos Byron Xicay 2 +Fenix Cibernetico Cristina Barrios 2 +``` + +Para "Dragones Digitales" el `COUNT(*)` deberia ser 3 (su numero real +de partidas), pero da 6 porque el `JOIN` con `jugadores` duplica cada +partida por cada uno de sus 2 jugadores antes de que `GROUP BY` los +agrupe; y el nombre de jugador mostrado ("Alejandra Chinchilla") es +arbitrario, no representa a todo el equipo. + +## Aprendizaje + +`GROUP BY` agrupa las filas que comparten el mismo valor en la +columna indicada, y las funciones de agregacion (`COUNT`, `SUM`, +`AVG`) calculan un resultado por cada grupo, no por cada fila +individual. `HAVING` filtra esos grupos ya formados (por ejemplo, +solo los equipos con promedio mayor a 70 puntos). Ademas, cualquier +columna que aparezca en el `SELECT` sin estar dentro de una funcion de +agregacion debe estar tambien en el `GROUP BY`; si no lo esta (como +`j.nombre_jugador` en el caso comentado), SQLite no lanza error pero +el valor mostrado es arbitrario y, si ademas el `JOIN` introduce +filas de mas (un equipo con varios jugadores), tambien distorsiona +los conteos y sumas del grupo. From b245925c90b71d62f368b583bb49690f005c054f Mon Sep 17 00:00:00 2001 From: MariaJoseMontepequeZet Date: Tue, 25 Aug 2026 14:19:27 -0600 Subject: [PATCH 2/2] feat(sql): resolver solicitud SQL ejercicio 090 (laboratorio quimico) --- .../solicitudes-sql/ejercicio-090/README.md | 87 +++++++++++++++ .../ejercicio-090/analisis/requerimiento.md | 102 ++++++++++++++++++ .../ejercicio-090/ddl/schema.sql | 82 ++++++++++++++ .../ejercicio-090/diagramas/diagrama-er.svg | 67 ++++++++++++ .../ejercicio-090/dml/inserts.sql | 78 ++++++++++++++ .../ejercicio-090/dml/operaciones.sql | 27 +++++ .../ejercicio-090/dql/consultas.sql | 54 ++++++++++ .../ejercicio-090/evidencias/resultados.md | 87 +++++++++++++++ 8 files changed, 584 insertions(+) create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-090/README.md create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-090/analisis/requerimiento.md create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-090/ddl/schema.sql create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-090/diagramas/diagrama-er.svg create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-090/dml/inserts.sql create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-090/dml/operaciones.sql create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-090/dql/consultas.sql create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-090/evidencias/resultados.md diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-090/README.md b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-090/README.md new file mode 100644 index 00000000..bd9fd5d7 --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-090/README.md @@ -0,0 +1,87 @@ +# Solicitud SQL - Ejercicio 090: Laboratorio Quimico + +**Nombre:** Maria Jose Montepeque +**Fecha:** 2026-08-25 + +## Solicitud del cliente + +Un laboratorio quimico registra formulas, muestras, reactivos y +resultados. El cliente quiere detectar errores (registros repetidos, +relaciones invalidas o valores fuera de rango) y 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 + +Ninguna muestra se borra una vez registrada: el historico solo se +corrige con `UPDATE` de estado. Es un nivel 5 (solicitud profesional): +ademas del modelo, se pide interpretar ambiguedad, normalizar datos, +documentar decisiones y crear al menos una vista SQL. El detalle +completo del analisis esta en +[analisis/requerimiento.md](analisis/requerimiento.md). + +## Que tablas cree y por que + +- `tecnicos`, `formulas`, `reactivos`: catalogos. +- `muestras`: historico, cada muestra recibida para analisis. +- `resultados`: historico, con `UNIQUE (id_muestra)` para que una + muestra nunca tenga dos resultados oficiales contradictorios. +- `detalle_reactivos`: tabla puente entre muestras y reactivos. + +## Vista SQL + +`vista_historial_muestra` (definida en +[ddl/schema.sql](ddl/schema.sql)) junta muestra, formula, tecnico y +resultado, respondiendo directamente "que paso y cuando paso" con +cada muestra. + +## Como se relacionan + +`tecnicos` 1:N `muestras`; `formulas` 1:N `muestras`; `muestras` 1:1 +`resultados`; `muestras` 1:N `detalle_reactivos`; `reactivos` 1:N +`detalle_reactivos`. El diagrama esta en +[diagramas/diagrama-er.svg](diagramas/diagrama-er.svg). + +## Que datos de prueba use + +3 tecnicos, 4 formulas, 5 reactivos, 6 muestras (2 `finalizada`, 1 +`rechazada`, 2 `en_analisis`, 1 `recibida`), 3 resultados y 7 lineas +de detalle, incluida una cargada por error en una muestra todavia +`en_analisis`. Tambien un `INSERT` comentado que reproduce el +problema de dos resultados oficiales para la misma muestra y debe +fallar. Detalle en [dml/inserts.sql](dml/inserts.sql). + +## Que operaciones de mantenimiento incluyo + +En [dml/operaciones.sql](dml/operaciones.sql): un `INSERT` adicional +(nueva muestra recibida), un `UPDATE` de estado (una muestra pasa de +`recibida` a `en_analisis`) y un `DELETE` controlado que corrige la +linea de reactivo agregada por error (solo mientras la muestra sigue +`recibida` o `en_analisis`). + +## Que consultas responden al cliente + +En [dql/consultas.sql](dql/consultas.sql): el historial completo +usando la vista, que muestras estan en analisis en este momento, que +reactivos se usaron en cada muestra (via `JOIN`), las muestras +ordenadas por fecha de recepcion, un `GROUP BY` del promedio de valor +medido por formula, y un reporte final con `HAVING` de que formulas +tienen muestras rechazadas, para decidir cuales revisar con el +proveedor de reactivos. + +## 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-090.db < ddl/schema.sql +sqlite3 ejercicio-090.db < dml/inserts.sql +sqlite3 ejercicio-090.db < dml/operaciones.sql +sqlite3 ejercicio-090.db < dql/consultas.sql +``` + +No suba archivos `.db`, `.sqlite`, `.sqlite3` ni `.dump`. diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-090/analisis/requerimiento.md b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-090/analisis/requerimiento.md new file mode 100644 index 00000000..ca836f2f --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-090/analisis/requerimiento.md @@ -0,0 +1,102 @@ +# Analisis del requerimiento - Ejercicio 090 + +## Solicitud entendida + +Un laboratorio quimico registra formulas, muestras, reactivos y +resultados. El cliente quiere detectar errores (registros repetidos, +relaciones invalidas o valores fuera de rango) y pidio convertir esa +operacion en una base de datos que permita consultar datos, corregir +estados, registrar movimientos y sacar reportes utiles. Es un nivel 5 +(solicitud profesional): ademas del modelo, se pide interpretar +ambiguedad, normalizar datos, documentar decisiones y crear al menos +una vista SQL. + +## Entidades detectadas + +| Entidad | Por que existe | Atributos importantes | +| --- | --- | --- | +| tecnicos | Catalogo: quien recibe y procesa cada muestra | nombre_tecnico, codigo_tecnico (unico) | +| formulas | Catalogo: contra que formula se prueba cada muestra | nombre_formula (unica), categoria | +| reactivos | Catalogo: cada reactivo disponible en el laboratorio | nombre_reactivo (unico), stock_disponible | +| muestras | Historico: cada muestra recibida para analisis | fecha_recepcion, estado | +| resultados | Historico: el resultado oficial de analizar una muestra especifica | valor_medido, veredicto | + +Se agrego `detalle_reactivos` como tabla puente entre `muestras` y +`reactivos` (relacion muchos a muchos: una muestra puede necesitar +varios reactivos para analizarse, y un mismo reactivo se usa en +muchas muestras distintas). + +## Relaciones detectadas + +| Relacion | Tipo | Explicacion | +| --- | --- | --- | +| tecnicos -> muestras | 1:N | Un tecnico puede recibir y procesar varias muestras. | +| formulas -> muestras | 1:N | Una formula se prueba en varias muestras a lo largo del tiempo. | +| muestras -> resultados | 1:1 | Cada muestra tiene, como mucho, un resultado oficial asociado. | +| muestras -> detalle_reactivos | 1:N | Una muestra usa varios reactivos durante su analisis. | +| reactivos -> detalle_reactivos | 1:N | Un reactivo se usa en muchas muestras distintas. | + +## Decisiones de modelado y ambiguedad interpretada + +- **"Detectar registros repetidos, relaciones invalidas o valores + fuera de rango" (la peticion central del cliente):** se resuelve con + `UNIQUE` en los catalogos y en la relacion muestra-resultado, con + `FOREIGN KEY` en cadena para que ninguna muestra, resultado o linea + de reactivo apunte a un registro que no existe, y con `CHECK` en + todos los valores numericos que no pueden ser negativos. +- **Vista SQL:** se crea `vista_historial_muestra`, que junta muestra, + formula, tecnico y resultado en una sola consulta, respondiendo + directamente "que paso y cuando paso" con cada muestra. +- **"Registrar movimientos" y "corregir estados":** las muestras nunca + se borran (son historico de laboratorio); solo cambian de estado con + `UPDATE`, y los reactivos cargados por error se corrigen con + `DELETE` controlado mientras la muestra sigue en analisis. +- **Ambiguedad no resuelta por el cliente:** no se detallo si el + laboratorio maneja lotes de reactivos con fecha de caducidad. Se + documenta como fuera del alcance de este nivel: el modelo controla + cantidad y stock, pero no vencimientos. + +## Reglas de negocio + +- Regla 1 (relaciones invalidas): toda muestra debe apuntar a una + formula real y a un tecnico real; todo resultado debe apuntar a una + muestra real; toda linea de `detalle_reactivos` debe apuntar a una + muestra real y a un reactivo real (`FOREIGN KEY` en cadena). +- Regla 2 (registros repetidos): `tecnicos.codigo_tecnico`, + `formulas.nombre_formula` y `reactivos.nombre_reactivo` no se + repiten (`UNIQUE`); una muestra no puede tener mas de un resultado + oficial (`UNIQUE (id_muestra)` en `resultados`); un reactivo no + puede aparecer dos veces como linea separada en la misma muestra + (`UNIQUE (id_muestra, id_reactivo)`). +- Regla 3 (valores fuera de rango): `reactivos.stock_disponible`, + `resultados.valor_medido` nunca negativos; + `detalle_reactivos.cantidad_usada` siempre mayor que 0 (`CHECK`). +- Regla 4: una muestra nace `'recibida'` y avanza a + `'en_analisis'`, `'finalizada'` o `'rechazada'` (`CHECK`); se + corrige con `UPDATE`. +- Regla 5: una linea de `detalle_reactivos` se puede quitar con + `DELETE` solo mientras la muestra sigue `'recibida'` o + `'en_analisis'` (todavia no hay resultado oficial). Una vez + `'finalizada'` o `'rechazada'`, sus reactivos ya son parte del + historico de auditoria y no se borran. + +## Supuestos + +- Se asume que el stock de reactivos se controla por cantidad total + disponible (no por lote individual), por eso vive directamente en + `reactivos.stock_disponible`. +- No se detallo si una muestra puede volver a analizarse tras ser + `'rechazada'`; se asume que una muestra rechazada requiere registrar + una muestra nueva, para el alcance de este nivel. + +## Preguntas que responde la base de datos + +1. Que paso con cada muestra (formula, tecnico, resultado), via la + vista `vista_historial_muestra`. +2. Que muestras estan en analisis en este momento. +3. Que reactivos se usaron en cada muestra y en que cantidad. +4. Como se ordenan las muestras por fecha de recepcion. +5. Que formulas tienen mas resultados registrados y cual es su valor + medido promedio. +6. Que formulas tienen muestras rechazadas, para decidir cuales + revisar con el proveedor de reactivos. diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-090/ddl/schema.sql b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-090/ddl/schema.sql new file mode 100644 index 00000000..38817325 --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-090/ddl/schema.sql @@ -0,0 +1,82 @@ +PRAGMA foreign_keys = ON; + +-- Ejercicio 090: Laboratorio Quimico +-- Modelo: tecnicos -> muestras (1:N); formulas -> muestras (1:N); +-- muestras -> resultados (1:1); muestras + reactivos -> +-- detalle_reactivos (1:N cada una). + +CREATE TABLE tecnicos ( + id_tecnico INTEGER PRIMARY KEY AUTOINCREMENT, + nombre_tecnico TEXT NOT NULL, + codigo_tecnico TEXT NOT NULL UNIQUE +); + +CREATE TABLE formulas ( + id_formula INTEGER PRIMARY KEY AUTOINCREMENT, + nombre_formula TEXT NOT NULL UNIQUE, + categoria TEXT NOT NULL +); + +CREATE TABLE reactivos ( + id_reactivo INTEGER PRIMARY KEY AUTOINCREMENT, + nombre_reactivo TEXT NOT NULL UNIQUE, + unidad_medida TEXT NOT NULL, + stock_disponible REAL NOT NULL DEFAULT 0 CHECK (stock_disponible >= 0) +); + +-- muestras: historico, nunca se borra. +CREATE TABLE muestras ( + id_muestra INTEGER PRIMARY KEY AUTOINCREMENT, + id_formula INTEGER NOT NULL, + id_tecnico INTEGER NOT NULL, + fecha_recepcion TEXT NOT NULL, + estado TEXT NOT NULL DEFAULT 'recibida' + CHECK (estado IN ('recibida', 'en_analisis', 'finalizada', 'rechazada')), + + FOREIGN KEY (id_formula) REFERENCES formulas (id_formula), + FOREIGN KEY (id_tecnico) REFERENCES tecnicos (id_tecnico) +); + +-- resultados: el UNIQUE sobre id_muestra garantiza como maximo un +-- resultado oficial por muestra, para que una auditoria nunca +-- encuentre resultados contradictorios. +CREATE TABLE resultados ( + id_resultado INTEGER PRIMARY KEY AUTOINCREMENT, + id_muestra INTEGER NOT NULL UNIQUE, + fecha_resultado TEXT NOT NULL, + valor_medido REAL NOT NULL CHECK (valor_medido >= 0), + veredicto TEXT NOT NULL CHECK (veredicto IN ('aprobado', 'rechazado')), + + FOREIGN KEY (id_muestra) REFERENCES muestras (id_muestra) +); + +-- detalle_reactivos: el UNIQUE compuesto impide registrar el mismo +-- reactivo dos veces como linea separada en la misma muestra. +CREATE TABLE detalle_reactivos ( + id_detalle INTEGER PRIMARY KEY AUTOINCREMENT, + id_muestra INTEGER NOT NULL, + id_reactivo INTEGER NOT NULL, + cantidad_usada REAL NOT NULL CHECK (cantidad_usada > 0), + + FOREIGN KEY (id_muestra) REFERENCES muestras (id_muestra), + FOREIGN KEY (id_reactivo) REFERENCES reactivos (id_reactivo), + UNIQUE (id_muestra, id_reactivo) +); + +-- Vista SQL (requerida en nivel 5): responde directamente "que paso y +-- cuando paso" con cada muestra, tal como pidio el cliente. +CREATE VIEW vista_historial_muestra AS + SELECT + m.id_muestra, + t.nombre_tecnico, + f.nombre_formula, + f.categoria, + m.fecha_recepcion, + m.estado, + r.fecha_resultado, + r.valor_medido, + r.veredicto + FROM muestras m + JOIN formulas f ON f.id_formula = m.id_formula + JOIN tecnicos t ON t.id_tecnico = m.id_tecnico + LEFT JOIN resultados r ON r.id_muestra = m.id_muestra; diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-090/diagramas/diagrama-er.svg b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-090/diagramas/diagrama-er.svg new file mode 100644 index 00000000..d133f3ae --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-090/diagramas/diagrama-er.svg @@ -0,0 +1,67 @@ + + + + + tecnicos + id_tecnico PK + codigo_tecnico UNIQUE + + + formulas + id_formula PK + nombre_formula UNIQUE + + + muestras + id_muestra PK + id_formula FK, id_tecnico FK + estado CHECK + + + resultados + id_resultado PK + id_muestra FK UNIQUE (1:1) + + + detalle_reactivos + id_detalle PK + id_muestra FK, id_reactivo FK + UNIQUE(muestra, reactivo) + + + reactivos + id_reactivo PK + nombre_reactivo UNIQUE + + + vista_historial_muestra + que paso y cuando paso + + + + 1:N + + + + 1:N + + + + 1:1 + + + + 1:N + + + + 1:N + diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-090/dml/inserts.sql b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-090/dml/inserts.sql new file mode 100644 index 00000000..7f60082c --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-090/dml/inserts.sql @@ -0,0 +1,78 @@ +PRAGMA foreign_keys = ON; + +-- Ejercicio 090: Laboratorio Quimico +-- Datos base: 3 tecnicos, 4 formulas, 5 reactivos, 6 muestras (2 +-- finalizadas, 1 rechazada, 2 en_analisis, 1 recibida), 3 resultados +-- y 7 lineas de detalle (incluye 1 cargada por error en una muestra +-- todavia en_analisis). + +INSERT INTO tecnicos (nombre_tecnico, codigo_tecnico) VALUES + ('Sofia Ramirez', 'T-001'), + ('Carlos Perez', 'T-002'), + ('Marta Lopez', 'T-003'); + +INSERT INTO formulas (nombre_formula, categoria) VALUES + ('Solucion Buffer pH7', 'control_calidad'), + ('Acido Sulfurico Diluido', 'sintesis'), + ('Cloruro de Sodio Estandar', 'control_calidad'), + ('Etanol 96%', 'sintesis'); + +INSERT INTO reactivos (nombre_reactivo, unidad_medida, stock_disponible) VALUES + ('Agua Destilada', 'L', 500), + ('Hidroxido de Sodio', 'kg', 50), + ('Acido Clorhidrico', 'L', 30), + ('Cloruro de Sodio', 'kg', 100), + ('Etanol Puro', 'L', 80); + +-- Muestra 1: Buffer pH7 de Sofia, finalizada y aprobada. +INSERT INTO muestras (id_formula, id_tecnico, fecha_recepcion, estado) VALUES + (1, 1, '2026-08-01', 'finalizada'); +INSERT INTO resultados (id_muestra, fecha_resultado, valor_medido, veredicto) VALUES + (1, '2026-08-02', 7.02, 'aprobado'); +INSERT INTO detalle_reactivos (id_muestra, id_reactivo, cantidad_usada) VALUES + (1, 1, 2); + +-- Muestra 2: Acido Sulfurico Diluido de Carlos, finalizada y aprobada. +INSERT INTO muestras (id_formula, id_tecnico, fecha_recepcion, estado) VALUES + (2, 2, '2026-08-02', 'finalizada'); +INSERT INTO resultados (id_muestra, fecha_resultado, valor_medido, veredicto) VALUES + (2, '2026-08-03', 95.50, 'aprobado'); +INSERT INTO detalle_reactivos (id_muestra, id_reactivo, cantidad_usada) VALUES + (2, 3, 1), + (2, 1, 1); + +-- Muestra 3: Cloruro de Sodio Estandar de Sofia, todavia en_analisis. +INSERT INTO muestras (id_formula, id_tecnico, fecha_recepcion, estado) VALUES + (3, 1, '2026-08-03', 'en_analisis'); +INSERT INTO detalle_reactivos (id_muestra, id_reactivo, cantidad_usada) VALUES + (3, 4, 0.5); + +-- Linea de reactivo cargada por error para la muestra 3 (todavia +-- 'en_analisis'): el tecnico anoto hidroxido de sodio, que esta +-- formula no necesita. Se corrige con DELETE en dml/operaciones.sql. +INSERT INTO detalle_reactivos (id_muestra, id_reactivo, cantidad_usada) VALUES + (3, 2, 0.3); + +-- Muestra 4: Etanol 96% de Marta, rechazada por pureza fuera de rango. +INSERT INTO muestras (id_formula, id_tecnico, fecha_recepcion, estado) VALUES + (4, 3, '2026-08-04', 'rechazada'); +INSERT INTO resultados (id_muestra, fecha_resultado, valor_medido, veredicto) VALUES + (4, '2026-08-05', 89.00, 'rechazado'); +INSERT INTO detalle_reactivos (id_muestra, id_reactivo, cantidad_usada) VALUES + (4, 5, 1); + +-- Muestra 5: Buffer pH7 de Carlos, recien recibida (sin analizar). +INSERT INTO muestras (id_formula, id_tecnico, fecha_recepcion, estado) VALUES + (1, 2, '2026-08-05', 'recibida'); + +-- Muestra 6: Acido Sulfurico Diluido de Marta, en_analisis. +INSERT INTO muestras (id_formula, id_tecnico, fecha_recepcion, estado) VALUES + (2, 3, '2026-08-06', 'en_analisis'); +INSERT INTO detalle_reactivos (id_muestra, id_reactivo, cantidad_usada) VALUES + (6, 3, 0.5); + +-- Caso comentado que debe fallar (queda comentado): registrar un +-- segundo resultado oficial para la muestra 1, exactamente el +-- problema de historico contradictorio que este UNIQUE esta disenado +-- para evitar. +-- INSERT INTO resultados (id_muestra, fecha_resultado, valor_medido, veredicto) VALUES (1, '2026-08-06', 6.90, 'aprobado'); diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-090/dml/operaciones.sql b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-090/dml/operaciones.sql new file mode 100644 index 00000000..48ff6a3d --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-090/dml/operaciones.sql @@ -0,0 +1,27 @@ +PRAGMA foreign_keys = ON; + +-- Ejercicio 090: Laboratorio Quimico +-- Operaciones de mantenimiento sobre los datos base. + +-- INSERT adicional: llega una muestra nueva de Cloruro de Sodio +-- Estandar, recibida por Carlos. +INSERT INTO muestras (id_formula, id_tecnico, fecha_recepcion, estado) VALUES + (3, 2, '2026-08-10', 'recibida'); + +-- UPDATE con WHERE: la muestra 5 ya empezo a analizarse. +UPDATE muestras +SET estado = 'en_analisis' +WHERE id_muestra = 5 AND estado = 'recibida'; + +-- DELETE controlado: la muestra 3 todavia esta 'en_analisis', asi que +-- es seguro corregir el hidroxido de sodio que se anoto por error +-- (esta formula solo necesitaba cloruro de sodio). +DELETE FROM detalle_reactivos +WHERE id_muestra = 3 AND id_reactivo = 2; + +-- Caso que debe fallar / no ser recomendable (queda comentado): +-- borrar una linea de reactivo de la muestra 1, que ya esta +-- 'finalizada' (parte del historico de auditoria). El DELETE de +-- arriba solo se aplico mientras la muestra 3 seguia 'en_analisis', +-- por diseno. +-- DELETE FROM detalle_reactivos WHERE id_muestra = 1 AND id_reactivo = 1; diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-090/dql/consultas.sql b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-090/dql/consultas.sql new file mode 100644 index 00000000..f92afc7c --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-090/dql/consultas.sql @@ -0,0 +1,54 @@ +.headers on +.mode column + +-- Ejercicio 090: Laboratorio Quimico +-- Consultas que responden preguntas reales del cliente. + +-- 1. Listado principal: se usa la vista vista_historial_muestra +-- (creada en ddl/schema.sql), que responde directamente "que paso y +-- cuando paso" con cada muestra. +SELECT * +FROM vista_historial_muestra; + +-- 2. Filtro por estado: que muestras estan en analisis en este momento. +SELECT id_muestra, id_formula, id_tecnico, fecha_recepcion +FROM muestras +WHERE estado = 'en_analisis'; + +-- 3. Consulta con JOIN: que reactivos se usaron en cada muestra y en +-- que cantidad. +SELECT m.id_muestra, f.nombre_formula, r.nombre_reactivo, dr.cantidad_usada, r.unidad_medida +FROM detalle_reactivos dr +JOIN muestras m ON m.id_muestra = dr.id_muestra +JOIN formulas f ON f.id_formula = m.id_formula +JOIN reactivos r ON r.id_reactivo = dr.id_reactivo +ORDER BY m.id_muestra; + +-- 4. Reporte ordenado por una metrica importante: muestras ordenadas +-- por fecha de recepcion. +SELECT id_muestra, fecha_recepcion, estado +FROM muestras +ORDER BY fecha_recepcion; + +-- 5. Conteo, suma o promedio: cuantos resultados tiene cada formula y +-- cual es su valor medido promedio (GROUP BY + JOIN). +SELECT f.nombre_formula, + COUNT(r.id_resultado) AS total_resultados, + ROUND(AVG(r.valor_medido), 2) AS promedio_valor +FROM formulas f +JOIN muestras m ON m.id_formula = f.id_formula +JOIN resultados r ON r.id_muestra = m.id_muestra +GROUP BY f.id_formula, f.nombre_formula +ORDER BY promedio_valor DESC; + +-- 6. Consulta final de decision para el cliente: que formulas tienen +-- muestras rechazadas, para decidir cuales revisar con el proveedor +-- de reactivos (GROUP BY + HAVING sobre un conteo condicional). +SELECT f.nombre_formula, + COUNT(m.id_muestra) AS total_muestras, + SUM(CASE WHEN m.estado = 'rechazada' THEN 1 ELSE 0 END) AS muestras_rechazadas +FROM formulas f +JOIN muestras m ON m.id_formula = f.id_formula +GROUP BY f.id_formula, f.nombre_formula +HAVING SUM(CASE WHEN m.estado = 'rechazada' THEN 1 ELSE 0 END) > 0 +ORDER BY muestras_rechazadas DESC; diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-090/evidencias/resultados.md b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-090/evidencias/resultados.md new file mode 100644 index 00000000..fddd5847 --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-090/evidencias/resultados.md @@ -0,0 +1,87 @@ +# Evidencias - Solicitudes SQL - Ejercicio 090 (Laboratorio Quimico) + +## 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-090.db < ddl/schema.sql +sqlite3 ejercicio-090.db < dml/inserts.sql +sqlite3 ejercicio-090.db < dml/operaciones.sql +sqlite3 ejercicio-090.db < dql/consultas.sql +``` + +## Resultados + +Datos base tras `dml/inserts.sql`: 3 tecnicos, 4 formulas, 5 +reactivos, 6 muestras (2 `finalizada`, 1 `rechazada`, 2 +`en_analisis`, 1 `recibida`), 3 resultados y 7 lineas de detalle +(incluye la cargada por error en la muestra 3, todavia +`en_analisis`). + +**Caso comentado verificado:** + +- `INSERT INTO resultados (id_muestra, ...) VALUES (1, ...);` (segundo resultado oficial para la muestra 1) → `UNIQUE constraint failed: resultados.id_muestra`. + +**1. Historial completo via `vista_historial_muestra` (ya con la +muestra 5 y la nueva muestra 7 tras `dml/operaciones.sql`):** + +```text +id_muestra | nombre_tecnico | nombre_formula | estado | veredicto +1 | Sofia Ramirez | Solucion Buffer pH7 | finalizada | aprobado +2 | Carlos Perez | Acido Sulfurico Diluido | finalizada | aprobado +3 | Sofia Ramirez | Cloruro de Sodio Estandar | en_analisis | (sin resultado) +4 | Marta Lopez | Etanol 96% | rechazada | rechazado +5 | Carlos Perez | Solucion Buffer pH7 | en_analisis | (sin resultado) +6 | Marta Lopez | Acido Sulfurico Diluido | en_analisis | (sin resultado) +7 | Carlos Perez | Cloruro de Sodio Estandar | recibida | (sin resultado) +``` + +**5. Promedio de valor medido por formula:** + +```text +nombre_formula total_resultados promedio_valor +Acido Sulfurico Diluido 1 95.5 +Etanol 96% 1 89.0 +Solucion Buffer pH7 1 7.02 +``` + +**6. Formulas con muestras rechazadas (decision para el cliente):** + +```text +nombre_formula total_muestras muestras_rechazadas +Etanol 96% 1 1 +``` + +Solo `Etanol 96%` aparece: es la unica formula con al menos una +muestra en estado `rechazada` (muestra 4, veredicto `rechazado` por +pureza fuera de rango), por lo que es la formula que el laboratorio +deberia revisar primero con su proveedor de reactivos. + +## Operaciones de mantenimiento verificadas + +- **INSERT adicional**: se registro la muestra 7 (Cloruro de Sodio + Estandar, `recibida`). Total de muestras: 6 -> 7. +- **UPDATE de estado**: `UPDATE muestras SET estado = 'en_analisis' WHERE id_muestra = 5 ...;` → la muestra 5 (Buffer pH7 de Carlos) paso de `recibida` a `en_analisis`. +- **DELETE controlado**: se elimino el hidroxido de sodio cargado por error en la muestra 3, mientras esta seguia `en_analisis`. Total de lineas de detalle: 7 -> 6. +- **Caso NO recomendable verificado**: `DELETE FROM detalle_reactivos WHERE id_muestra = 1 AND id_reactivo = 1;` (muestra ya `finalizada`) se probo por separado y SQLite **no** lo bloquea, porque no existe una regla de negocio como esta expresada en el `CHECK` del esquema. Por eso la regla se documenta y se respeta por convencion en `dml/operaciones.sql`, tal como se aplico el `DELETE` real solo sobre la muestra 3 (todavia `en_analisis`). + +## Aprendizaje + +El modelo separa catalogos (`tecnicos`, `formulas`, `reactivos`) de +historico (`muestras`, `resultados`, `detalle_reactivos`), y usa +`UNIQUE (id_muestra)` en `resultados` para proteger el requisito +central del cliente: nunca puede haber dos resultados oficiales +contradictorios para la misma muestra. El reporte de formulas con +muestras rechazadas se construyo con `GROUP BY` y una expresion +`CASE` dentro de `SUM(...)` para contar condicionalmente solo las +muestras `rechazada` de cada formula, y `HAVING` para quedarse solo +con las formulas donde ese conteo es mayor a cero. Tambien se +confirmo que una regla de negocio como "no borrar reactivos de una +muestra ya finalizada" no la garantiza la base de datos por si sola +(SQLite permitio el `DELETE` de prueba): protegerla realmente +requeriria un `TRIGGER`, que queda fuera del alcance de este +ejercicio (`GROUP BY` / nivel 5 de solicitudes), por lo que aqui se +documenta como convencion de uso.