From a1537e7d6a6e0ea5261287b0f83f8db47097f737 Mon Sep 17 00:00:00 2001 From: MariaJoseMontepequeZet Date: Mon, 24 Aug 2026 16:44:43 -0600 Subject: [PATCH 1/2] feat(sql): resolver ejercicio 76 --- .../maria-montepeque/ejercicio-76/README.md | 85 +++++++++++++++++++ .../ejercicio-76/ddl/schema.sql | 34 ++++++++ .../ejercicio-76/dml/inserts.sql | 60 +++++++++++++ .../ejercicio-76/dql/consultas.sql | 47 ++++++++++ .../ejercicio-76/evidencias/resultados.md | 72 ++++++++++++++++ 5 files changed, 298 insertions(+) create mode 100644 resoluciones/maria-montepeque/ejercicio-76/README.md create mode 100644 resoluciones/maria-montepeque/ejercicio-76/ddl/schema.sql create mode 100644 resoluciones/maria-montepeque/ejercicio-76/dml/inserts.sql create mode 100644 resoluciones/maria-montepeque/ejercicio-76/dql/consultas.sql create mode 100644 resoluciones/maria-montepeque/ejercicio-76/evidencias/resultados.md diff --git a/resoluciones/maria-montepeque/ejercicio-76/README.md b/resoluciones/maria-montepeque/ejercicio-76/README.md new file mode 100644 index 00000000..24bea74b --- /dev/null +++ b/resoluciones/maria-montepeque/ejercicio-76/README.md @@ -0,0 +1,85 @@ +# Ejercicio 76: UPDATE Nivel Aplicado + +**Nombre:** Maria Jose Montepeque +**Fecha:** 2026-08-24 + +## Tema central + +UPDATE + +## Descripcion del problema + +Un sistema de registro de campers administra inscripciones a rutas de +entrenamiento con cupo limitado. Cada vez que un camper se inscribe o +cancela, el cupo disponible de la ruta debe corregirse de inmediato +con `UPDATE`, y al final se necesita poder confirmar que ese cupo +guardado sigue siendo confiable: un caso de negocio con validacion +final, propio del nivel aplicado. + +## Tablas y relaciones + +- `campers`: catalogo de campers registrados. +- `rutas`: catalogo de rutas, cada una con `cupo_maximo` y + `cupo_disponible` (este ultimo se corrige con `UPDATE`). +- `inscripciones`: relaciona un camper con una ruta. `campers` 1—N + `inscripciones`; `rutas` 1—N `inscripciones`. + +## Uso de UPDATE + +En `dml/inserts.sql`, por cada inscripcion nueva: + +1. `UPDATE` con expresion: `cupo_disponible = cupo_disponible - 1` en + la ruta correspondiente, repetido 6 veces (una por cada + inscripcion), demostrando que el mismo patron de `UPDATE` se aplica + de forma consistente cada vez que ocurre el evento de negocio. +2. Cancelacion: dos `UPDATE` en cadena, uno por tabla. Primero + `inscripciones.estado = 'cancelada'`, despues + `cupo_disponible = cupo_disponible + 1` en la ruta, para devolver + el cupo liberado. + +La consulta 5 en `dql/consultas.sql` es el reporte final: compara el +`cupo_disponible` que quedo guardado contra un calculo independiente +(`cupo_maximo` menos el conteo de inscripciones `activa`), confirmando +que los `UPDATE` mantuvieron la columna consistente. + +## 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.cupo_disponible >= 0`, + `inscripciones.estado IN (...)`. +- `DEFAULT` en `rutas.cupo_maximo`, `inscripciones.estado` y + `fecha_inscripcion`. +- `PRAGMA foreign_keys = ON;` activado al inicio del script. + +## Caso que falla / no recomendable (comentado en `dml/inserts.sql`) + +Cumbre Extrema tiene cupo para solo 3 campers; una vez lleno +(`cupo_disponible = 0`), inscribir a un cuarto camper y restar 1 al +cupo dejaria la columna en -1, lo que viola el `CHECK` de +`cupo_disponible >= 0`. Se valido con Python (`sqlite3`), reproduciendo +el estado exacto de la secuencia en ese punto: lanza +`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: 6 campers, 3 rutas, 6 inscripciones (5 activas, 1 + cancelada). Reporte final: cupo guardado y cupo calculado coinciden + en las 3 rutas. + +## Como ejecutar + +```bash +sqlite3 ejercicio-76.db < ddl/schema.sql +sqlite3 ejercicio-76.db < dml/inserts.sql +sqlite3 ejercicio-76.db < dql/consultas.sql +``` + +No suba archivos `.db`, `.sqlite` ni `.sqlite3`. diff --git a/resoluciones/maria-montepeque/ejercicio-76/ddl/schema.sql b/resoluciones/maria-montepeque/ejercicio-76/ddl/schema.sql new file mode 100644 index 00000000..756ee213 --- /dev/null +++ b/resoluciones/maria-montepeque/ejercicio-76/ddl/schema.sql @@ -0,0 +1,34 @@ +PRAGMA foreign_keys = ON; + +-- Ejercicio 76: UPDATE Nivel Aplicado +-- Tema central: UPDATE +-- Contexto: registro de campers inscritos en rutas de entrenamiento, +-- con cupo limitado por ruta. + +CREATE TABLE campers ( + id_camper INTEGER PRIMARY KEY AUTOINCREMENT, + nombre TEXT NOT NULL, + email TEXT NOT NULL UNIQUE +); + +-- rutas: cupo_disponible es el campo que se corrige con UPDATE cada +-- vez que alguien se inscribe o cancela. Nunca puede ser negativo +-- (eso significaria mas inscritos que cupo). +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), + cupo_disponible INTEGER NOT NULL CHECK (cupo_disponible >= 0) +); + +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-76/dml/inserts.sql b/resoluciones/maria-montepeque/ejercicio-76/dml/inserts.sql new file mode 100644 index 00000000..7c2409e5 --- /dev/null +++ b/resoluciones/maria-montepeque/ejercicio-76/dml/inserts.sql @@ -0,0 +1,60 @@ +PRAGMA foreign_keys = ON; + +-- Ejercicio 76: UPDATE Nivel Aplicado +-- Caso de negocio: cada inscripcion activa debe restar 1 al cupo +-- disponible de su ruta, y cada cancelacion debe devolverlo. La +-- consulta 5 en dql/consultas.sql es la validacion final: confirma +-- que cupo_disponible siempre coincide con +-- cupo_maximo - inscripciones activas. + +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'); + +-- Rutas con cupo_disponible = cupo_maximo al inicio (nadie inscrito +-- todavia). Cumbre Extrema tiene cupo reducido a proposito para poder +-- demostrar el caso de ruta llena. +INSERT INTO rutas (nombre_ruta, cupo_maximo, cupo_disponible) VALUES + ('Cumbre Extrema', 3, 3), + ('Sendero del Canon', 5, 5), + ('Ruta del Volcan', 10, 10); + +-- Inscripciones en Cumbre Extrema: 3 campers llenan el cupo. Cada +-- INSERT va seguido de su UPDATE correspondiente, que resta 1 al +-- cupo_disponible de esa ruta con una expresion. +INSERT INTO inscripciones (id_camper, id_ruta) VALUES (1, 1); +UPDATE rutas SET cupo_disponible = cupo_disponible - 1 WHERE id_ruta = 1; + +INSERT INTO inscripciones (id_camper, id_ruta) VALUES (2, 1); +UPDATE rutas SET cupo_disponible = cupo_disponible - 1 WHERE id_ruta = 1; + +INSERT INTO inscripciones (id_camper, id_ruta) VALUES (3, 1); +UPDATE rutas SET cupo_disponible = cupo_disponible - 1 WHERE id_ruta = 1; + +-- Caso comentado que debe fallar (no ser recomendable), dejar +-- comentado: Cumbre Extrema ya esta llena (cupo_disponible = 0); +-- inscribir a un cuarto camper y restar 1 dejaria el cupo en -1, lo +-- que viola el CHECK de cupo_disponible >= 0. +-- INSERT INTO inscripciones (id_camper, id_ruta) VALUES (4, 1); +-- UPDATE rutas SET cupo_disponible = cupo_disponible - 1 WHERE id_ruta = 1; + +-- Inscripciones en Sendero del Canon. +INSERT INTO inscripciones (id_camper, id_ruta) VALUES (4, 2); +UPDATE rutas SET cupo_disponible = cupo_disponible - 1 WHERE id_ruta = 2; + +INSERT INTO inscripciones (id_camper, id_ruta) VALUES (5, 2); +UPDATE rutas SET cupo_disponible = cupo_disponible - 1 WHERE id_ruta = 2; + +-- Inscripcion en Ruta del Volcan. +INSERT INTO inscripciones (id_camper, id_ruta) VALUES (6, 3); +UPDATE rutas SET cupo_disponible = cupo_disponible - 1 WHERE id_ruta = 3; + +-- Mario Ixtabalan cancela su inscripcion en Cumbre Extrema: se libera +-- un cupo. Dos UPDATE, uno por tabla: primero el estado de la +-- inscripcion, despues el cupo de la ruta. +UPDATE inscripciones SET estado = 'cancelada' WHERE id_inscripcion = 2; +UPDATE rutas SET cupo_disponible = cupo_disponible + 1 WHERE id_ruta = 1; diff --git a/resoluciones/maria-montepeque/ejercicio-76/dql/consultas.sql b/resoluciones/maria-montepeque/ejercicio-76/dql/consultas.sql new file mode 100644 index 00000000..b9a8b03d --- /dev/null +++ b/resoluciones/maria-montepeque/ejercicio-76/dql/consultas.sql @@ -0,0 +1,47 @@ +.headers on +.mode column + +-- Ejercicio 76: UPDATE Nivel Aplicado +-- 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, + i.fecha_inscripcion +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 estado. +SELECT estado, COUNT(*) AS total +FROM inscripciones +GROUP BY estado; + +-- 5. Caso de negocio con reporte final (nivel aplicado): se compara +-- el cupo_disponible que quedo despues de todos los UPDATE contra el +-- cupo que deberia haber, calculado desde cero solo con +-- cupo_maximo y el conteo de inscripciones activas. Si coinciden, +-- los UPDATE de cupo cumplieron su proposito. +SELECT r.nombre_ruta, + r.cupo_maximo, + r.cupo_disponible AS cupo_guardado, + r.cupo_maximo - ( + SELECT COUNT(*) + FROM inscripciones i + WHERE i.id_ruta = r.id_ruta AND i.estado = 'activa' + ) AS cupo_calculado +FROM rutas r +ORDER BY r.nombre_ruta; diff --git a/resoluciones/maria-montepeque/ejercicio-76/evidencias/resultados.md b/resoluciones/maria-montepeque/ejercicio-76/evidencias/resultados.md new file mode 100644 index 00000000..33406013 --- /dev/null +++ b/resoluciones/maria-montepeque/ejercicio-76/evidencias/resultados.md @@ -0,0 +1,72 @@ +# Evidencias - Ejercicio 76 + +## Tema + +UPDATE + +## 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-76.db < ddl/schema.sql +sqlite3 ejercicio-76.db < dml/inserts.sql +sqlite3 ejercicio-76.db < dql/consultas.sql +``` + +## Resultados + +Estado final de `rutas` tras `dml/inserts.sql` (6 inscripciones, 1 +cancelada, con sus `UPDATE` de cupo correspondientes): + +```text +id_ruta | nombre_ruta | cupo_maximo | cupo_disponible +1 | Cumbre Extrema | 3 | 1 +2 | Sendero del Canon | 5 | 3 +3 | Ruta del Volcan | 10 | 9 +``` + +**Caso comentado verificado** (probado en el punto exacto de la +secuencia donde aparece comentado, justo despues de llenar Cumbre +Extrema con 3 inscripciones, cuando `cupo_disponible` ya esta en 0): + +- `INSERT INTO inscripciones ...; UPDATE rutas SET cupo_disponible = cupo_disponible - 1 WHERE id_ruta = 1;` → `CHECK constraint failed: cupo_disponible >= 0`. + +**4. Resumen: inscripciones por estado:** + +```text +estado total +activa 5 +cancelada 1 +``` + +**5. Reporte final del caso de negocio (nivel aplicado): cupo +guardado en la tabla vs. cupo calculado desde cero con las +inscripciones activas:** + +```text +nombre_ruta cupo_maximo | cupo_guardado | cupo_calculado +Cumbre Extrema 3 | 1 | 1 +Ruta del Volcan 10 | 9 | 9 +Sendero del Canon 5 | 3 | 3 +``` + +Las tres rutas coinciden exactamente entre lo que quedo guardado por +los `UPDATE` y lo que se recalcula desde cero contando inscripciones +`activa`: los `UPDATE` de cupo cumplieron su proposito. + +## Aprendizaje + +Cada `UPDATE` de este ejercicio corrige un valor derivado +(`cupo_disponible`) a partir de un evento real (una inscripcion nueva +o una cancelacion), usando la propia columna como base +(`cupo_disponible ± 1`) en vez de recalcular todo desde cero cada vez. +El `CHECK (cupo_disponible >= 0)` actua como una red de seguridad: si +algun `UPDATE` intentara dejar mas inscritos que cupo disponible, la +base de datos lo rechaza en el momento, como se confirmo con el caso +comentado. La consulta 5 es la validacion final propia del nivel +aplicado: demuestra, con una subconsulta independiente, que la columna +mantenida a mano con `UPDATE` sigue siendo consistente con la realidad +de las inscripciones activas. From 6b21c202efbace0f28a743a7c130e5045de7dee2 Mon Sep 17 00:00:00 2001 From: MariaJoseMontepequeZet Date: Mon, 24 Aug 2026 16:44:43 -0600 Subject: [PATCH 2/2] feat(sql): resolver solicitud SQL ejercicio 076 (cafeteria campus) --- .../solicitudes-sql/ejercicio-076/README.md | 80 +++++++++++++++++++ .../ejercicio-076/analisis/requerimiento.md | 78 ++++++++++++++++++ .../ejercicio-076/ddl/schema.sql | 55 +++++++++++++ .../ejercicio-076/diagramas/.gitkeep | 0 .../ejercicio-076/diagramas/diagrama-er.svg | 56 +++++++++++++ .../ejercicio-076/dml/inserts.sql | 61 ++++++++++++++ .../ejercicio-076/dml/operaciones.sql | 26 ++++++ .../ejercicio-076/dql/consultas.sql | 49 ++++++++++++ .../ejercicio-076/evidencias/resultados.md | 53 ++++++++++++ 9 files changed, 458 insertions(+) create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-076/README.md create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-076/analisis/requerimiento.md create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-076/ddl/schema.sql create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-076/diagramas/.gitkeep create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-076/diagramas/diagrama-er.svg create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-076/dml/inserts.sql create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-076/dml/operaciones.sql create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-076/dql/consultas.sql create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-076/evidencias/resultados.md diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-076/README.md b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-076/README.md new file mode 100644 index 00000000..ab6cc9f4 --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-076/README.md @@ -0,0 +1,80 @@ +# Solicitud SQL - Ejercicio 076: Cafeteria Campus + +**Nombre:** Maria Jose Montepeque +**Fecha:** 2026-08-24 + +## Solicitud del cliente + +Una cafeteria cerca del campus quiere controlar productos, ventas +rapidas y pagos de estudiantes. El cliente quiere diferenciar +catalogos, operaciones y resultados para no mezclar informacion +permanente con movimientos. 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 + +La peticion central es de diseno: separar claramente lo permanente +(productos, clientes) de lo operativo (ventas y su detalle) y de lo +resultante (pagos), en vez de mezclar todo en una sola tabla. 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 + +- `productos`: catalogo permanente de lo que vende la cafeteria. +- `clientes`: catalogo permanente de estudiantes. +- `ventas`: operacion, el encabezado de cada venta rapida. +- `detalle_ventas`: operacion, cada linea de producto dentro de una + venta. Aqui esta el `UNIQUE (id_venta, id_producto)` que impide + registrar el mismo producto dos veces en la misma venta. +- `pagos`: resultado de una venta. El `UNIQUE (id_venta)` garantiza un + solo pago oficial por venta. + +## Como se relacionan + +`clientes` 1:N `ventas`; `ventas` 1:N `detalle_ventas`; `productos` +1:N `detalle_ventas`; `ventas` 1:1 `pagos`. El diagrama esta en +[diagramas/diagrama-er.svg](diagramas/diagrama-er.svg). + +## Que datos de prueba use + +5 productos, 4 clientes, 4 ventas (2 `cerrada` con pago desde el +inicio, 2 `abierta`) y 8 lineas de detalle, incluida una linea +cargada por error en una venta que todavia no tenia pago. Tambien un +`INSERT` comentado que reproduce el problema de duplicar un producto +en la misma venta y debe fallar. Detalle en +[dml/inserts.sql](dml/inserts.sql). + +## Que operaciones de mantenimiento incluyo + +En [dml/operaciones.sql](dml/operaciones.sql): un `DELETE` controlado +que corrige la linea agregada por error (solo posible porque esa +venta seguia `abierta` y sin pago), un `UPDATE` de estado (la venta se +cobra y se cierra) y el registro de su pago oficial. + +## Que consultas responden al cliente + +En [dql/consultas.sql](dql/consultas.sql): que lineas de venta existen +(JOIN producto-venta), en que estado esta cada venta, que cliente tiene +mas actividad (mas gastado), las lineas ordenadas por subtotal, y un +reporte con `GROUP BY` + `HAVING` de los productos mas vendidos, para +decidir cuales reabastecer primero. + +## 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-076.db < ddl/schema.sql +sqlite3 ejercicio-076.db < dml/inserts.sql +sqlite3 ejercicio-076.db < dml/operaciones.sql +sqlite3 ejercicio-076.db < dql/consultas.sql +``` + +No suba archivos `.db`, `.sqlite`, `.sqlite3` ni `.dump`. diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-076/analisis/requerimiento.md b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-076/analisis/requerimiento.md new file mode 100644 index 00000000..93884071 --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-076/analisis/requerimiento.md @@ -0,0 +1,78 @@ +# Analisis del requerimiento - Ejercicio 076 + +## Solicitud entendida + +Una cafeteria cerca del campus quiere controlar productos, ventas +rapidas y pagos de estudiantes. El cliente quiere diferenciar +catalogos, operaciones y resultados para no mezclar informacion +permanente con movimientos: eso significa separar claramente lo que +no cambia seguido (productos, clientes) de lo que si (ventas, su +detalle, pagos). Se necesita una base de datos que permita consultar +datos, corregir estados, registrar movimientos y sacar reportes +utiles. + +## Entidades detectadas + +| Entidad | Por que existe | Atributos importantes | +| --- | --- | --- | +| productos | Catalogo permanente: cada producto que vende la cafeteria | nombre_producto (unico), precio, categoria | +| clientes | Catalogo permanente: cada estudiante que compra | nombre_cliente, carnet_estudiante (unico) | +| ventas | Operacion: cada venta rapida, encabezado del ticket | fecha_venta, estado | +| detalle_ventas | Operacion: cada linea de producto dentro de una venta | cantidad, precio_unitario | +| pagos | Resultado: el pago de una venta, uno por venta | monto, metodo_pago | + +## Relaciones detectadas + +| Relacion | Tipo | Explicacion | +| --- | --- | --- | +| clientes -> ventas | 1:N | Un cliente puede tener varias ventas. | +| ventas -> detalle_ventas | 1:N | Una venta tiene una linea por cada producto distinto que se llevo. | +| productos -> detalle_ventas | 1:N | Un producto aparece en muchas ventas distintas. | +| ventas -> pagos | 1:1 | Cada venta tiene, como mucho, un pago oficial (evita registrar el mismo pago dos veces). | + +## Reglas de negocio + +Esta separacion catalogo/operacion/resultado es justo lo que pidio el +cliente: + +- Regla 1 (relaciones invalidas): toda linea de `detalle_ventas` debe + apuntar a una venta y a un producto reales; todo pago debe apuntar a + una venta real (`FOREIGN KEY` en cadena). +- Regla 2 (registros repetidos): `productos.nombre_producto` y + `clientes.carnet_estudiante` no se repiten (`UNIQUE`); un producto + no puede aparecer dos veces como linea separada en la misma venta + (`UNIQUE (id_venta, id_producto)`); una venta no puede tener mas de + un pago (`UNIQUE (id_venta)` en `pagos`). +- Regla 3 (valores fuera de rango): `detalle_ventas.cantidad` siempre + mayor que 0; `productos.precio`, `detalle_ventas.precio_unitario` y + `pagos.monto` nunca negativos (`CHECK`). +- Regla 4: una venta nace `'abierta'` y avanza a `'cerrada'` o + `'cancelada'` (`CHECK`); se corrige con `UPDATE` cuando se cobra o se + anula. +- Regla 5: el total de una venta no se guarda como numero fijo, se + calcula sumando `cantidad * precio_unitario` de sus lineas (ver + reporte en `dql/consultas.sql`). Si una linea se agrego por error + mientras la venta sigue `'abierta'` (sin pago todavia), se corrige + con `DELETE`; una vez que la venta tiene pago, sus lineas ya no se + tocan. + +## Supuestos + +- El cliente no detallo si el precio del producto en el catalogo + puede diferir del precio cobrado en una venta especifica; se guarda + `precio_unitario` tambien en `detalle_ventas` para conservar el + precio real de esa venta aunque el precio del catalogo cambie + despues. +- Se asume que cada venta se paga completa de una sola vez (no hay + pagos parciales), por eso `pagos` es 1:1 con `ventas`. +- No se detallo un limite de productos por venta; se asume que puede + tener cualquier cantidad de lineas distintas. + +## Preguntas que responde la base de datos + +1. Que lineas de venta existen, con que producto y que venta. +2. Que ventas estan abiertas, cerradas o canceladas. +3. Que cliente tiene mas actividad (mas gastado en total). +4. Como se ordenan las lineas de venta por su subtotal. +5. Que productos son los mas vendidos, para decidir cuales + reabastecer primero. diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-076/ddl/schema.sql b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-076/ddl/schema.sql new file mode 100644 index 00000000..cf16ed1e --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-076/ddl/schema.sql @@ -0,0 +1,55 @@ +PRAGMA foreign_keys = ON; + +-- Ejercicio 076: Cafeteria Campus +-- Modelo separado en catalogos (productos, clientes), operacion +-- (ventas, detalle_ventas) y resultado (pagos), tal como pidio el +-- cliente. + +CREATE TABLE productos ( + id_producto INTEGER PRIMARY KEY AUTOINCREMENT, + nombre_producto TEXT NOT NULL UNIQUE, + precio REAL NOT NULL CHECK (precio >= 0), + categoria TEXT NOT NULL CHECK (categoria IN ('bebida', 'comida', 'snack')) +); + +CREATE TABLE clientes ( + id_cliente INTEGER PRIMARY KEY AUTOINCREMENT, + nombre_cliente TEXT NOT NULL, + carnet_estudiante TEXT NOT NULL UNIQUE +); + +CREATE TABLE ventas ( + id_venta INTEGER PRIMARY KEY AUTOINCREMENT, + id_cliente INTEGER NOT NULL, + fecha_venta TEXT NOT NULL DEFAULT (datetime('now')), + estado TEXT NOT NULL DEFAULT 'abierta' + CHECK (estado IN ('abierta', 'cerrada', 'cancelada')), + + FOREIGN KEY (id_cliente) REFERENCES clientes (id_cliente) +); + +-- detalle_ventas: el UNIQUE compuesto impide que un producto quede +-- registrado dos veces como linea separada en la misma venta. +CREATE TABLE detalle_ventas ( + id_detalle INTEGER PRIMARY KEY AUTOINCREMENT, + id_venta INTEGER NOT NULL, + id_producto INTEGER NOT NULL, + cantidad INTEGER NOT NULL CHECK (cantidad > 0), + precio_unitario REAL NOT NULL CHECK (precio_unitario >= 0), + + FOREIGN KEY (id_venta) REFERENCES ventas (id_venta), + FOREIGN KEY (id_producto) REFERENCES productos (id_producto), + UNIQUE (id_venta, id_producto) +); + +-- pagos: el UNIQUE sobre id_venta garantiza como maximo un pago +-- oficial por venta (relacion 1:1). +CREATE TABLE pagos ( + id_pago INTEGER PRIMARY KEY AUTOINCREMENT, + id_venta INTEGER NOT NULL UNIQUE, + monto REAL NOT NULL CHECK (monto >= 0), + metodo_pago TEXT NOT NULL CHECK (metodo_pago IN ('efectivo', 'tarjeta', 'transferencia')), + fecha_pago TEXT NOT NULL DEFAULT (datetime('now')), + + FOREIGN KEY (id_venta) REFERENCES ventas (id_venta) +); diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-076/diagramas/.gitkeep b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-076/diagramas/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-076/diagramas/diagrama-er.svg b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-076/diagramas/diagrama-er.svg new file mode 100644 index 00000000..db92e104 --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-076/diagramas/diagrama-er.svg @@ -0,0 +1,56 @@ + + + + + clientes + id_cliente PK + nombre_cliente + carnet_estudiante UNIQUE + + + productos + id_producto PK + nombre_producto UNIQUE + precio, categoria CHECK + + + ventas + id_venta PK + id_cliente FK + estado CHECK + + + detalle_ventas + id_detalle PK + id_venta FK, id_producto FK + cantidad, precio_unitario + UNIQUE(venta, producto) + + + pagos + id_pago PK + id_venta FK UNIQUE (1:1) + monto, metodo_pago CHECK + + + + 1:N + + + + 1:N + + + + 1:N + + + + 1:1 + diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-076/dml/inserts.sql b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-076/dml/inserts.sql new file mode 100644 index 00000000..f5add809 --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-076/dml/inserts.sql @@ -0,0 +1,61 @@ +PRAGMA foreign_keys = ON; + +-- Ejercicio 076: Cafeteria Campus +-- Datos base: 5 productos, 4 clientes, 4 ventas (2 cerradas con pago +-- desde el inicio, 1 abierta que se cierra despues, 1 abierta con una +-- linea cargada por error que se corrige antes de cerrarla) y sus +-- lineas de detalle. + +INSERT INTO productos (nombre_producto, precio, categoria) VALUES + ('Cafe Americano', 15.00, 'bebida'), + ('Te Helado', 12.00, 'bebida'), + ('Sandwich Jamon', 35.00, 'comida'), + ('Muffin Chocolate', 18.00, 'comida'), + ('Papas Fritas', 20.00, 'snack'); + +INSERT INTO clientes (nombre_cliente, carnet_estudiante) VALUES + ('Manuel Estrada', 'CAR-1001'), + ('Alejandra Chinchilla', 'CAR-1002'), + ('Byron Xicay', 'CAR-1003'), + ('Cristina Barrios', 'CAR-1004'); + +-- Venta 1: Manuel, ya cerrada y pagada. +INSERT INTO ventas (id_cliente, estado) VALUES + (1, 'cerrada'); +INSERT INTO detalle_ventas (id_venta, id_producto, cantidad, precio_unitario) VALUES + (1, 1, 2, 15.00), + (1, 4, 1, 18.00); +INSERT INTO pagos (id_venta, monto, metodo_pago) VALUES + (1, 48.00, 'efectivo'); + +-- Venta 2: Alejandra, ya cerrada y pagada. +INSERT INTO ventas (id_cliente, estado) VALUES + (2, 'cerrada'); +INSERT INTO detalle_ventas (id_venta, id_producto, cantidad, precio_unitario) VALUES + (2, 3, 1, 35.00), + (2, 5, 2, 20.00); +INSERT INTO pagos (id_venta, monto, metodo_pago) VALUES + (2, 75.00, 'tarjeta'); + +-- Venta 3: Byron, todavia abierta. Se cierra y se paga en +-- dml/operaciones.sql. +INSERT INTO ventas (id_cliente, estado) VALUES + (3, 'abierta'); +INSERT INTO detalle_ventas (id_venta, id_producto, cantidad, precio_unitario) VALUES + (3, 2, 1, 12.00), + (3, 5, 1, 20.00); + +-- Venta 4: Cristina, todavia abierta. Se agrego por error un +-- Sandwich Jamon que la clienta no pidio; se corrige con DELETE en +-- dml/operaciones.sql mientras la venta sigue abierta (sin pago +-- todavia, es seguro corregirla). +INSERT INTO ventas (id_cliente, estado) VALUES + (4, 'abierta'); +INSERT INTO detalle_ventas (id_venta, id_producto, cantidad, precio_unitario) VALUES + (4, 1, 1, 15.00), + (4, 3, 1, 35.00); + +-- Caso comentado que debe fallar (queda comentado): registrar de +-- nuevo Cafe Americano como otra linea separada en la venta 1, +-- exactamente el problema que este UNIQUE esta disenado para evitar. +-- INSERT INTO detalle_ventas (id_venta, id_producto, cantidad, precio_unitario) VALUES (1, 1, 1, 15.00); diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-076/dml/operaciones.sql b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-076/dml/operaciones.sql new file mode 100644 index 00000000..a60b23e8 --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-076/dml/operaciones.sql @@ -0,0 +1,26 @@ +PRAGMA foreign_keys = ON; + +-- Ejercicio 076: Cafeteria Campus +-- Operaciones de mantenimiento sobre los datos base. + +-- 1 DELETE controlado: la venta 4 todavia esta 'abierta' (sin pago), +-- asi que es seguro corregir el Sandwich Jamon que se agrego por +-- error. Si la venta ya tuviera pago, esta linea no se tocaria. +DELETE FROM detalle_ventas +WHERE id_venta = 4 AND id_producto = 3; + +-- 1 UPDATE de estado: la venta 3 se cobra y se cierra. +UPDATE ventas +SET estado = 'cerrada' +WHERE id_venta = 3 AND estado = 'abierta'; + +-- Pago oficial de la venta 3, ahora que ya esta cerrada (monto = +-- 12.00 + 20.00, la suma de sus lineas). +INSERT INTO pagos (id_venta, monto, metodo_pago) VALUES + (3, 32.00, 'transferencia'); + +-- Caso que debe fallar / no ser recomendable (queda comentado): +-- borrar una linea de la venta 1, que ya tiene pago registrado (dato +-- oficial de la cafeteria). El DELETE de arriba solo se aplico +-- mientras la venta 4 seguia 'abierta' y sin pago, por diseno. +-- DELETE FROM detalle_ventas WHERE id_venta = 1 AND id_producto = 1; diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-076/dql/consultas.sql b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-076/dql/consultas.sql new file mode 100644 index 00000000..cf4d58a2 --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-076/dql/consultas.sql @@ -0,0 +1,49 @@ +.headers on +.mode column + +-- Ejercicio 076: Cafeteria Campus +-- Consultas que responden preguntas reales del cliente. + +-- 1. Que registros principales existen: todas las lineas de venta con +-- su producto y su venta. +SELECT dv.id_detalle, + p.nombre_producto, + v.id_venta, + dv.cantidad, + dv.precio_unitario, + (dv.cantidad * dv.precio_unitario) AS subtotal +FROM detalle_ventas dv +JOIN productos p ON p.id_producto = dv.id_producto +JOIN ventas v ON v.id_venta = dv.id_venta; + +-- 2. Que ventas estan abiertas, cerradas o canceladas. +SELECT id_venta, id_cliente, estado +FROM ventas +ORDER BY estado; + +-- 3. Que cliente tiene mas actividad (mas gastado en total). +SELECT c.nombre_cliente, + SUM(dv.cantidad * dv.precio_unitario) AS total_gastado +FROM clientes c +JOIN ventas v ON v.id_cliente = c.id_cliente +JOIN detalle_ventas dv ON dv.id_venta = v.id_venta +GROUP BY c.id_cliente, c.nombre_cliente +ORDER BY total_gastado DESC, c.nombre_cliente; + +-- 4. Lineas de venta ordenadas por subtotal, de mayor a menor. +SELECT p.nombre_producto, dv.cantidad, dv.precio_unitario, + (dv.cantidad * dv.precio_unitario) AS subtotal +FROM detalle_ventas dv +JOIN productos p ON p.id_producto = dv.id_producto +ORDER BY subtotal DESC; + +-- 5. Reporte para decision de negocio: productos mas vendidos por +-- cantidad total, para decidir cuales reabastecer primero (GROUP BY +-- + HAVING). +SELECT p.nombre_producto, + SUM(dv.cantidad) AS unidades_vendidas +FROM detalle_ventas dv +JOIN productos p ON p.id_producto = dv.id_producto +GROUP BY p.id_producto, p.nombre_producto +HAVING SUM(dv.cantidad) >= 2 +ORDER BY unidades_vendidas DESC; diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-076/evidencias/resultados.md b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-076/evidencias/resultados.md new file mode 100644 index 00000000..757280c4 --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-076/evidencias/resultados.md @@ -0,0 +1,53 @@ +# Evidencias - Solicitudes SQL - Ejercicio 076 (Cafeteria Campus) + +## 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-076.db < ddl/schema.sql +sqlite3 ejercicio-076.db < dml/inserts.sql +sqlite3 ejercicio-076.db < dml/operaciones.sql +sqlite3 ejercicio-076.db < dql/consultas.sql +``` + +## Resultados + +Datos base tras `dml/inserts.sql`: 5 productos, 4 clientes, 4 ventas +(2 `cerrada` con pago, 2 `abierta`) y 8 lineas de detalle (incluye la +del Sandwich Jamon agregado por error en la venta 4). + +**Caso comentado verificado:** + +- `INSERT INTO detalle_ventas (id_venta, id_producto, ...) VALUES (1, 1, ...);` (segunda linea de Cafe Americano en la venta 1) → `UNIQUE constraint failed: detalle_ventas.id_venta, detalle_ventas.id_producto`. + +**3. Cliente con mas actividad (total gastado):** + +```text +nombre_cliente total_gastado +Alejandra Chinchilla 75.0 +Manuel Estrada 48.0 +Byron Xicay 32.0 +Cristina Barrios 15.0 +``` + +**5. Productos mas vendidos por unidades (minimo 2 unidades, para +decidir cuales reabastecer primero):** + +```text +nombre_producto unidades_vendidas +Cafe Americano 3 +Papas Fritas 3 +``` + +## Operaciones de mantenimiento verificadas + +- **DELETE controlado**: se elimino el Sandwich Jamon agregado por error en la venta 4, mientras esta seguia `abierta` y sin pago. La venta 4 quedo solo con Cafe Americano (1 unidad). +- `UPDATE ventas SET estado = 'cerrada' WHERE id_venta = 3 ...;` → la venta de Byron Xicay se cobro y se cerro. +- Se registro el pago oficial de la venta 3 (`transferencia`, Q32.00 = 12.00 + 20.00 de sus lineas), ahora que ya esta cerrada. Total de pagos: 2 -> 3. + +## Aprendizaje + +El `UNIQUE (id_venta, id_producto)` en `detalle_ventas` y el `UNIQUE (id_venta)` en `pagos` son las restricciones que resuelven directamente lo que pidio el cliente: separar catalogos de operaciones de resultados sin permitir registros duplicados en ninguna de las tres capas. El `DELETE` controlado solo se aplico mientras la venta 4 seguia `abierta` y sin pago; una vez que una venta tiene su pago oficial (como la 1, 2 y 3), sus lineas ya no se tocan. El reporte de productos mas vendidos (`GROUP BY` + `HAVING`) demuestra que, con el modelo separado en capas, es facil responder una pregunta de negocio real sin mezclar informacion permanente con movimientos.