From ed0a00e9532a64b09562490bd1b33912f5e08bee Mon Sep 17 00:00:00 2001 From: MariaJoseMontepequeZet Date: Tue, 25 Aug 2026 07:15:00 -0600 Subject: [PATCH 1/2] feat(sql): resolver ejercicio 83 --- .../maria-montepeque/ejercicio-83/README.md | 76 +++++++++++++++++++ .../ejercicio-83/ddl/schema.sql | 29 +++++++ .../ejercicio-83/dml/inserts.sql | 24 ++++++ .../ejercicio-83/dql/consultas.sql | 47 ++++++++++++ .../ejercicio-83/evidencias/resultados.md | 57 ++++++++++++++ 5 files changed, 233 insertions(+) create mode 100644 resoluciones/maria-montepeque/ejercicio-83/README.md create mode 100644 resoluciones/maria-montepeque/ejercicio-83/ddl/schema.sql create mode 100644 resoluciones/maria-montepeque/ejercicio-83/dml/inserts.sql create mode 100644 resoluciones/maria-montepeque/ejercicio-83/dql/consultas.sql create mode 100644 resoluciones/maria-montepeque/ejercicio-83/evidencias/resultados.md diff --git a/resoluciones/maria-montepeque/ejercicio-83/README.md b/resoluciones/maria-montepeque/ejercicio-83/README.md new file mode 100644 index 00000000..1cb5ffdd --- /dev/null +++ b/resoluciones/maria-montepeque/ejercicio-83/README.md @@ -0,0 +1,76 @@ +# Ejercicio 83: WHERE Nivel Basico + +**Nombre:** Maria Jose Montepeque +**Fecha:** 2026-08-24 + +## Tema central + +WHERE + +## Descripcion del problema + +Una cafeteria necesita filtrar sus ventas de distintas formas: por +nombre de producto, por rango de precio, por fecha y combinando varias +condiciones a la vez, para poder responder preguntas puntuales sin +revisar todos los registros a mano. + +## Tablas y relaciones + +- `clientes`: catalogo de clientes. +- `productos`: catalogo de productos, con precio y categoria. +- `ventas`: relaciona un cliente con un producto en una fecha. + `clientes` 1—N `ventas`; `productos` 1—N `ventas`. + +## Uso de WHERE + +En `dql/consultas.sql`: + +1. Filtro por texto: `WHERE nombre_producto LIKE 'Ca%'` encuentra los + productos cuyo nombre empieza con "Ca" (Cafe Americano, + Cappuccino), usando el comodin `%`. +2. Filtro por numero: `WHERE precio BETWEEN 12 AND 18` selecciona + productos de rango de precio medio. +3. Filtro por fecha simulada: `WHERE fecha_venta >= '2026-08-02'` + compara fechas guardadas como texto en formato ISO, que ordenan + igual que fechas reales. +4. Operadores logicos combinados: la consulta 5 junta `BETWEEN`, + comparacion de fecha e `IN` con `AND`, de forma que una fila solo + aparece si cumple las tres condiciones a la vez. + +## Otras restricciones aplicadas + +- `PRIMARY KEY` autoincremental en las 3 tablas. +- `FOREIGN KEY`: `ventas.id_cliente`, `ventas.id_producto`. +- `NOT NULL` en todas las columnas obligatorias. +- `UNIQUE`: `clientes.telefono`, `productos.nombre_producto`. +- `CHECK`: `productos.precio >= 0`, `productos.categoria IN (...)`, + `ventas.cantidad > 0`. +- `DEFAULT` en `ventas.fecha_venta`. +- `PRAGMA foreign_keys = ON;` activado al inicio del script. + +## Caso que falla / no recomendable (comentado en `dql/consultas.sql`) + +`SELECT * FROM productos WHERE preci > 15;` falla porque `preci` no +es el nombre real de la columna (falta la "o" de `precio`). Se valido +con Python (`sqlite3`): lanza `no such column: preci`. + +## 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 clientes, 5 productos, 6 ventas. El filtro + combinado de la consulta 5 deja una sola venta que cumple las tres + condiciones a la vez. + +## Como ejecutar + +```bash +sqlite3 ejercicio-83.db < ddl/schema.sql +sqlite3 ejercicio-83.db < dml/inserts.sql +sqlite3 ejercicio-83.db < dql/consultas.sql +``` + +No suba archivos `.db`, `.sqlite` ni `.sqlite3`. diff --git a/resoluciones/maria-montepeque/ejercicio-83/ddl/schema.sql b/resoluciones/maria-montepeque/ejercicio-83/ddl/schema.sql new file mode 100644 index 00000000..c9c1c8be --- /dev/null +++ b/resoluciones/maria-montepeque/ejercicio-83/ddl/schema.sql @@ -0,0 +1,29 @@ +PRAGMA foreign_keys = ON; + +-- Ejercicio 83: WHERE Nivel Basico +-- Tema central: WHERE +-- Contexto: ventas diarias de una cafeteria. + +CREATE TABLE clientes ( + id_cliente INTEGER PRIMARY KEY AUTOINCREMENT, + nombre_cliente TEXT NOT NULL, + telefono TEXT NOT NULL UNIQUE +); + +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')) +); + +CREATE TABLE ventas ( + id_venta INTEGER PRIMARY KEY AUTOINCREMENT, + id_cliente INTEGER NOT NULL, + id_producto INTEGER NOT NULL, + cantidad INTEGER NOT NULL CHECK (cantidad > 0), + fecha_venta TEXT NOT NULL DEFAULT (date('now')), + + FOREIGN KEY (id_cliente) REFERENCES clientes (id_cliente), + FOREIGN KEY (id_producto) REFERENCES productos (id_producto) +); diff --git a/resoluciones/maria-montepeque/ejercicio-83/dml/inserts.sql b/resoluciones/maria-montepeque/ejercicio-83/dml/inserts.sql new file mode 100644 index 00000000..58f92a5d --- /dev/null +++ b/resoluciones/maria-montepeque/ejercicio-83/dml/inserts.sql @@ -0,0 +1,24 @@ +PRAGMA foreign_keys = ON; + +-- Ejercicio 83: WHERE Nivel Basico +-- Datos de prueba. + +INSERT INTO clientes (nombre_cliente, telefono) VALUES + ('Manuel Estrada', '5555-9001'), + ('Alejandra Chinchilla', '5555-9002'), + ('Byron Xicay', '5555-9003'); + +INSERT INTO productos (nombre_producto, precio, categoria) VALUES + ('Cafe Americano', 15.00, 'bebida'), + ('Cappuccino', 20.00, 'bebida'), + ('Te Chai', 12.00, 'bebida'), + ('Croissant', 18.00, 'comida'), + ('Sandwich Jamon', 35.00, 'comida'); + +INSERT INTO ventas (id_cliente, id_producto, cantidad, fecha_venta) VALUES + (1, 1, 2, '2026-08-01'), + (2, 2, 1, '2026-08-01'), + (3, 3, 3, '2026-08-02'), + (1, 4, 1, '2026-08-02'), + (2, 5, 1, '2026-08-03'), + (3, 1, 2, '2026-08-03'); diff --git a/resoluciones/maria-montepeque/ejercicio-83/dql/consultas.sql b/resoluciones/maria-montepeque/ejercicio-83/dql/consultas.sql new file mode 100644 index 00000000..3a343510 --- /dev/null +++ b/resoluciones/maria-montepeque/ejercicio-83/dql/consultas.sql @@ -0,0 +1,47 @@ +.headers on +.mode column + +-- Ejercicio 83: WHERE Nivel Basico +-- Consultas de validacion. + +-- 1. Mostrar todos los datos principales. +SELECT v.id_venta, c.nombre_cliente, p.nombre_producto, v.cantidad, v.fecha_venta +FROM ventas v +JOIN clientes c ON c.id_cliente = v.id_cliente +JOIN productos p ON p.id_producto = v.id_producto; + +-- 2. WHERE por texto: productos cuyo nombre empieza con "Ca" +-- (Cafe Americano, Cappuccino), usando LIKE con comodin. +SELECT nombre_producto, precio +FROM productos +WHERE nombre_producto LIKE 'Ca%'; + +-- 3. Consulta con ORDER BY: ventas ordenadas por fecha. +SELECT id_venta, fecha_venta +FROM ventas +ORDER BY fecha_venta; + +-- 4. Conteo o resumen: ventas por categoria de producto. +SELECT p.categoria, COUNT(*) AS total_ventas +FROM ventas v +JOIN productos p ON p.id_producto = v.id_producto +GROUP BY p.categoria; + +-- 5. Validacion especifica de WHERE: filtro por numero (BETWEEN), +-- por fecha (comparacion de texto en formato ISO) y con operadores +-- logicos combinados (AND, IN), todo en la misma consulta. +-- - precio BETWEEN 12 AND 18: solo productos de rango medio. +-- - fecha_venta >= '2026-08-02': solo ventas desde esa fecha. +-- - id_cliente IN (1, 2): solo esos dos clientes. +SELECT v.id_venta, c.nombre_cliente, p.nombre_producto, p.precio, v.fecha_venta +FROM ventas v +JOIN clientes c ON c.id_cliente = v.id_cliente +JOIN productos p ON p.id_producto = v.id_producto +WHERE p.precio BETWEEN 12 AND 18 + AND v.fecha_venta >= '2026-08-02' + AND v.id_cliente IN (1, 2); + +-- Caso comentado que debe fallar (no ser recomendable), dejar +-- comentado: escribir mal el nombre de una columna en el WHERE +-- (typo: "preci" en vez de "precio"). +-- SELECT * FROM productos WHERE preci > 15; diff --git a/resoluciones/maria-montepeque/ejercicio-83/evidencias/resultados.md b/resoluciones/maria-montepeque/ejercicio-83/evidencias/resultados.md new file mode 100644 index 00000000..8b3be465 --- /dev/null +++ b/resoluciones/maria-montepeque/ejercicio-83/evidencias/resultados.md @@ -0,0 +1,57 @@ +# Evidencias - Ejercicio 83 + +## Tema + +WHERE + +## 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-83.db < ddl/schema.sql +sqlite3 ejercicio-83.db < dml/inserts.sql +sqlite3 ejercicio-83.db < dql/consultas.sql +``` + +## Resultados + +**2. Productos cuyo nombre empieza con "Ca" (`LIKE 'Ca%'`):** + +```text +nombre_producto precio +Cafe Americano 15.0 +Cappuccino 20.0 +``` + +**Caso comentado verificado:** + +- `SELECT * FROM productos WHERE preci > 15;` → `no such column: preci` (falta la "o" en `precio`). + +**5. Filtro combinado (numero + fecha + operador logico IN):** + +```text +id_venta | nombre_cliente | nombre_producto | precio | fecha_venta +4 | Manuel Estrada | Croissant | 18.0 | 2026-08-02 +``` + +Solo la venta 4 cumple las tres condiciones a la vez: producto con +precio entre 12 y 18 (Croissant, 18.00), fecha desde el 2026-08-02 en +adelante, y cliente entre los ids 1 o 2 (Manuel Estrada). Las demas +ventas quedan fuera porque fallan al menos una condicion (por ejemplo, +la venta 2 es de Cappuccino a 20.00, fuera del rango de precio). + +## Aprendizaje + +`WHERE` filtra con distintos tipos de dato y distintos operadores en +una sola condicion: texto con `LIKE` y comodines (`%`), numeros con +`BETWEEN`, fechas simuladas como texto ISO comparadas con `>=` +(funciona porque `'2026-08-02'` ordena igual como texto que como +fecha), y varias condiciones combinadas con `AND` e `IN`. Cuando se +combinan varias condiciones con `AND`, una fila solo aparece en el +resultado si cumple todas a la vez, lo que reduce el resultado a un +subconjunto muy especifico. El caso comentado recuerda que un error de +escritura en el nombre de una columna dentro de `WHERE` tambien hace +fallar la consulta completa. From 35a99e1a08ec2a08599ed37a7d1fa5565a3af760 Mon Sep 17 00:00:00 2001 From: MariaJoseMontepequeZet Date: Tue, 25 Aug 2026 07:15:01 -0600 Subject: [PATCH 2/2] feat(sql): resolver solicitud SQL ejercicio 083 (viajes y paracaidismo) --- .../solicitudes-sql/ejercicio-083/README.md | 86 ++++++++++++++++++ .../ejercicio-083/analisis/requerimiento.md | 89 +++++++++++++++++++ .../ejercicio-083/ddl/schema.sql | 75 ++++++++++++++++ .../ejercicio-083/diagramas/.gitkeep | 0 .../ejercicio-083/diagramas/diagrama-er.svg | 58 ++++++++++++ .../ejercicio-083/dml/inserts.sql | 53 +++++++++++ .../ejercicio-083/dml/operaciones.sql | 26 ++++++ .../ejercicio-083/dql/consultas.sql | 39 ++++++++ .../ejercicio-083/evidencias/resultados.md | 65 ++++++++++++++ 9 files changed, 491 insertions(+) create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-083/README.md create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-083/analisis/requerimiento.md create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-083/ddl/schema.sql create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-083/diagramas/.gitkeep create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-083/diagramas/diagrama-er.svg create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-083/dml/inserts.sql create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-083/dml/operaciones.sql create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-083/dql/consultas.sql create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-083/evidencias/resultados.md diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-083/README.md b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-083/README.md new file mode 100644 index 00000000..11226f28 --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-083/README.md @@ -0,0 +1,86 @@ +# Solicitud SQL - Ejercicio 083: Viajes y Paracaidismo + +**Nombre:** Maria Jose Montepeque +**Fecha:** 2026-08-24 + +## Solicitud del cliente + +Una agencia vende experiencias de viaje, turismo y saltos en +paracaidas. El cliente quiere evitar registros incompletos porque +despues no puede hacer reportes confiables. Pidio convertir esa +operacion en una base de datos que permita consultar datos, corregir +estados, registrar movimientos y sacar reportes utiles. + +## Que entendi de la solicitud + +El problema no es "guardar reservas", es garantizar que ninguna +reserva quede duplicada y que el estado de sus pagos siempre sea +visible y confiable, incluso cuando falta informacion (una reserva sin +pago todavia). 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 + +- `clientes`: catalogo de clientes. +- `experiencias`: catalogo de experiencias disponibles. +- `instructores`: catalogo de instructores. +- `reservas`: tabla transaccional. `UNIQUE (id_cliente, id_experiencia, + fecha_reserva)` impide registrar la misma reserva dos veces. +- `pagos`: resultado de una reserva. `UNIQUE (id_reserva)` garantiza + un solo pago oficial por reserva. + +## Vista SQL + +`vista_resumen_reservas` (definida en +[ddl/schema.sql](ddl/schema.sql)) junta reserva, cliente, experiencia, +instructor y pago con `LEFT JOIN`. Esto ataca directamente el problema +de "reportes no confiables": una reserva sin pago no desaparece del +reporte, aparece con `monto_pagado = NULL`, visible y explicito. + +## Como se relacionan + +`clientes` 1:N `reservas`; `experiencias` 1:N `reservas`; +`instructores` 1:N `reservas`; `reservas` 1:1 `pagos`. El diagrama +esta en [diagramas/diagrama-er.svg](diagramas/diagrama-er.svg). + +## Que datos de prueba use + +4 clientes, 3 experiencias, 2 instructores, 4 reservas (2 `realizada` +con pago, 1 `confirmada` sin pago, 1 marcada `realizada` por error con +un pago que se corrige despues) y 3 pagos. Tambien un `INSERT` +comentado que reproduce el problema de duplicar una reserva 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 reserva que se marco realizada por error pasa a `cancelada`) y un +`DELETE` controlado que elimina el pago invalido de esa reserva, sin +tocar ningun pago de una reserva ya `realizada`. + +## Que consultas responden al cliente + +En [dql/consultas.sql](dql/consultas.sql): el resumen completo de +reservas usando la vista, en que estado esta cada reserva, que cliente +tiene mas reservas, las reservas ordenadas por fecha, y un reporte con +`GROUP BY` + `HAVING` (tambien sobre la vista) de ingresos totales por +tipo de experiencia, para decidir en cual invertir mas promocion. + +## 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-083.db < ddl/schema.sql +sqlite3 ejercicio-083.db < dml/inserts.sql +sqlite3 ejercicio-083.db < dml/operaciones.sql +sqlite3 ejercicio-083.db < dql/consultas.sql +``` + +No suba archivos `.db`, `.sqlite`, `.sqlite3` ni `.dump`. diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-083/analisis/requerimiento.md b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-083/analisis/requerimiento.md new file mode 100644 index 00000000..03aa8ea8 --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-083/analisis/requerimiento.md @@ -0,0 +1,89 @@ +# Analisis del requerimiento - Ejercicio 083 + +## Solicitud entendida + +Una agencia vende experiencias de viaje, turismo y saltos en +paracaidas. El cliente quiere evitar registros incompletos porque +despues no puede hacer reportes confiables: eso significa que el +modelo debe garantizar que cada reserva tenga, como maximo, un pago +oficial (nunca cero pagos duplicados ni dos pagos contradictorios), y +que las reservas repetidas por error no contaminen los reportes. 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 | +| --- | --- | --- | +| clientes | Catalogo: cada cliente que reserva una experiencia | nombre_cliente, telefono (unico) | +| experiencias | Catalogo: cada experiencia disponible (salto, tour, buceo) | nombre_experiencia (unica), tipo, precio | +| instructores | Catalogo: cada instructor que guia una experiencia | nombre_instructor (unico), certificacion | +| reservas | Tabla transaccional: un cliente reserva una experiencia con un instructor | fecha_reserva, estado | +| pagos | Resultado: el pago de una reserva, uno por reserva | monto, metodo_pago | + +## Relaciones detectadas + +| Relacion | Tipo | Explicacion | +| --- | --- | --- | +| clientes -> reservas | 1:N | Un cliente puede tener varias reservas. | +| experiencias -> reservas | 1:N | Una experiencia se reserva muchas veces. | +| instructores -> reservas | 1:N | Un instructor guia muchas reservas distintas. | +| reservas -> pagos | 1:1 | Cada reserva tiene, como mucho, un pago oficial. | + +## Decisiones de modelado y ambiguedad interpretada + +- **Registros incompletos/duplicados (el problema central del + cliente):** se traduce en dos restricciones concretas: + `UNIQUE (id_cliente, id_experiencia, fecha_reserva)` en `reservas` + (evita registrar la misma reserva dos veces por error de captura), y + `UNIQUE (id_reserva)` en `pagos` (evita que una reserva tenga dos + pagos, que dejaria el reporte de ingresos poco confiable). +- **Vista SQL:** se crea `vista_resumen_reservas`, que junta reserva, + cliente, experiencia, instructor y pago (si existe) con `LEFT JOIN`. + Esto ataca directamente el problema de "reportes no confiables": + una reserva sin pago todavia no desaparece del reporte, aparece con + `monto_pagado = NULL`, visible y explicito, en vez de quedar oculta + por un `JOIN` normal. +- **Ambiguedad no resuelta por el cliente:** no se detallo si un + instructor puede guiar dos experiencias distintas el mismo dia a la + misma hora (conflicto de horario). Se documenta como una regla + deseable pero fuera del alcance de este nivel: validar solapamiento + de horarios entre reservas requeria comparar filas distintas de la + misma tabla, lo que en SQLite exige un `TRIGGER`, no un `CHECK` + simple. + +## Reglas de negocio + +- Regla 1 (relaciones invalidas): toda reserva debe apuntar a un + cliente, una experiencia y un instructor reales; todo pago debe + apuntar a una reserva real (`FOREIGN KEY` en cadena). +- Regla 2 (registros repetidos/incompletos): ver arriba. +- Regla 3 (valores fuera de rango): `experiencias.precio` y + `pagos.monto` nunca negativos (`CHECK`). +- Regla 4: una reserva nace `'programada'` y avanza a `'confirmada'`, + `'realizada'` o `'cancelada'` (`CHECK`); se corrige con `UPDATE`. +- Regla 5: un pago solo se elimina con `DELETE` cuando la reserva a la + que pertenece se cancela y ese pago resulto ser un error (por + ejemplo, se proceso el cobro de una reserva que en realidad no se + confirmo). Un pago de una reserva `'realizada'` nunca se borra, + porque ya es un resultado oficial. + +## Supuestos + +- El cliente no detallo si el precio de una experiencia puede variar + por reserva (por ejemplo, descuentos); se guarda el monto real + cobrado en `pagos.monto`, separado del precio de catalogo en + `experiencias.precio`. +- Se asume que un instructor puede repetirse en varias reservas de la + misma experiencia sin restriccion adicional. + +## Preguntas que responde la base de datos + +1. Que reservas existen, con su cliente, experiencia, instructor y + pago (via la vista `vista_resumen_reservas`). +2. Que reservas estan programadas, confirmadas, realizadas o + canceladas. +3. Que cliente tiene mas reservas (ranking de actividad). +4. Como se ordenan las reservas por fecha. +5. Que tipo de experiencia genero mas ingresos, para decidir en cual + invertir mas promocion. diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-083/ddl/schema.sql b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-083/ddl/schema.sql new file mode 100644 index 00000000..66ea7339 --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-083/ddl/schema.sql @@ -0,0 +1,75 @@ +PRAGMA foreign_keys = ON; + +-- Ejercicio 083: Viajes y Paracaidismo +-- Modelo: clientes + experiencias + instructores -> reservas (1:N +-- cada una); reservas -> pagos (1:1). + +CREATE TABLE clientes ( + id_cliente INTEGER PRIMARY KEY AUTOINCREMENT, + nombre_cliente TEXT NOT NULL, + telefono TEXT NOT NULL UNIQUE +); + +CREATE TABLE experiencias ( + id_experiencia INTEGER PRIMARY KEY AUTOINCREMENT, + nombre_experiencia TEXT NOT NULL UNIQUE, + tipo TEXT NOT NULL CHECK (tipo IN ('paracaidismo', 'tour', 'buceo')), + precio REAL NOT NULL CHECK (precio >= 0) +); + +CREATE TABLE instructores ( + id_instructor INTEGER PRIMARY KEY AUTOINCREMENT, + nombre_instructor TEXT NOT NULL UNIQUE, + certificacion TEXT NOT NULL +); + +-- reservas: el UNIQUE compuesto impide registrar la misma reserva dos +-- veces (mismo cliente, misma experiencia, misma fecha), que es +-- exactamente el problema de registros incompletos/duplicados que +-- describio el cliente. +CREATE TABLE reservas ( + id_reserva INTEGER PRIMARY KEY AUTOINCREMENT, + id_cliente INTEGER NOT NULL, + id_experiencia INTEGER NOT NULL, + id_instructor INTEGER NOT NULL, + fecha_reserva TEXT NOT NULL, + estado TEXT NOT NULL DEFAULT 'programada' + CHECK (estado IN ('programada', 'confirmada', 'realizada', 'cancelada')), + + FOREIGN KEY (id_cliente) REFERENCES clientes (id_cliente), + FOREIGN KEY (id_experiencia) REFERENCES experiencias (id_experiencia), + FOREIGN KEY (id_instructor) REFERENCES instructores (id_instructor), + UNIQUE (id_cliente, id_experiencia, fecha_reserva) +); + +-- pagos: el UNIQUE sobre id_reserva garantiza como maximo un pago +-- oficial por reserva. +CREATE TABLE pagos ( + id_pago INTEGER PRIMARY KEY AUTOINCREMENT, + id_reserva INTEGER NOT NULL UNIQUE, + monto REAL NOT NULL CHECK (monto >= 0), + metodo_pago TEXT NOT NULL CHECK (metodo_pago IN ('tarjeta', 'transferencia')), + fecha_pago TEXT NOT NULL DEFAULT (datetime('now')), + + FOREIGN KEY (id_reserva) REFERENCES reservas (id_reserva) +); + +-- Vista SQL (requerida en nivel 5): resumen legible de cada reserva, +-- con LEFT JOIN a pagos para que una reserva sin pago todavia siga +-- siendo visible (monto_pagado = NULL) en vez de desaparecer del +-- reporte, evitando reportes poco confiables. +CREATE VIEW vista_resumen_reservas AS + SELECT + r.id_reserva, + c.nombre_cliente, + e.nombre_experiencia, + e.tipo, + i.nombre_instructor, + r.fecha_reserva, + r.estado, + p.monto AS monto_pagado + FROM reservas r + JOIN clientes c ON c.id_cliente = r.id_cliente + JOIN experiencias e ON e.id_experiencia = r.id_experiencia + JOIN instructores i ON i.id_instructor = r.id_instructor + LEFT JOIN pagos p ON p.id_reserva = r.id_reserva; diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-083/diagramas/.gitkeep b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-083/diagramas/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-083/diagramas/diagrama-er.svg b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-083/diagramas/diagrama-er.svg new file mode 100644 index 00000000..bfe1149f --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-083/diagramas/diagrama-er.svg @@ -0,0 +1,58 @@ + + + + + clientes + id_cliente PK + telefono UNIQUE + + + experiencias + id_experiencia PK + nombre UNIQUE, tipo, precio + + + reservas + id_reserva PK + id_cliente/experiencia/instructor FK + UNIQUE(cliente,exp,fecha) + + + instructores + id_instructor PK + nombre_instructor UNIQUE + + + pagos + id_pago PK + id_reserva FK UNIQUE (1:1) + monto, metodo_pago + + + vista_resumen_reservas + LEFT JOIN con pagos + + + + 1:N + + + + 1:N + + + + 1:N + + + + 1:1 + diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-083/dml/inserts.sql b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-083/dml/inserts.sql new file mode 100644 index 00000000..f1645b5b --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-083/dml/inserts.sql @@ -0,0 +1,53 @@ +PRAGMA foreign_keys = ON; + +-- Ejercicio 083: Viajes y Paracaidismo +-- Datos base: 4 clientes, 3 experiencias, 2 instructores, 4 reservas +-- (2 realizadas con pago, 1 confirmada sin pago todavia, 1 marcada +-- realizada por error con un pago que se debe corregir) y sus pagos. + +INSERT INTO clientes (nombre_cliente, telefono) VALUES + ('Manuel Estrada', '5555-7101'), + ('Alejandra Chinchilla', '5555-7102'), + ('Byron Xicay', '5555-7103'), + ('Cristina Barrios', '5555-7104'); + +INSERT INTO experiencias (nombre_experiencia, tipo, precio) VALUES + ('Salto en Tandem', 'paracaidismo', 1800.00), + ('Tour Volcan Pacaya', 'tour', 450.00), + ('Buceo en Arrecife', 'buceo', 900.00); + +INSERT INTO instructores (nombre_instructor, certificacion) VALUES + ('Hugo Marroquin', 'USPA nivel 2'), + ('Cristina Barrios Guia', 'PADI Open Water'); + +-- Reserva 1: Manuel, Salto en Tandem, realizada. +INSERT INTO reservas (id_cliente, id_experiencia, id_instructor, fecha_reserva, estado) VALUES + (1, 1, 1, '2026-08-01', 'realizada'); +INSERT INTO pagos (id_reserva, monto, metodo_pago) VALUES + (1, 1800.00, 'tarjeta'); + +-- Reserva 2: Alejandra, Tour Volcan Pacaya, realizada. +INSERT INTO reservas (id_cliente, id_experiencia, id_instructor, fecha_reserva, estado) VALUES + (2, 2, 2, '2026-08-02', 'realizada'); +INSERT INTO pagos (id_reserva, monto, metodo_pago) VALUES + (2, 450.00, 'transferencia'); + +-- Reserva 3: Byron, Buceo en Arrecife, confirmada (todavia sin pago). +INSERT INTO reservas (id_cliente, id_experiencia, id_instructor, fecha_reserva, estado) VALUES + (3, 3, 2, '2026-08-05', 'confirmada'); + +-- Reserva 4: Cristina, Salto en Tandem. Se marco 'realizada' y se +-- proceso el pago, pero despues se confirmo que la clienta cancelo +-- antes de saltar (el equipo de ventas se adelanto). Se corrige en +-- dml/operaciones.sql. +INSERT INTO reservas (id_cliente, id_experiencia, id_instructor, fecha_reserva, estado) VALUES + (4, 1, 2, '2026-08-06', 'realizada'); +INSERT INTO pagos (id_reserva, monto, metodo_pago) VALUES + (4, 1800.00, 'tarjeta'); + +-- Caso comentado que debe fallar (queda comentado): registrar de +-- nuevo la reserva de Manuel Estrada para Salto en Tandem el mismo +-- dia, exactamente el problema de registros duplicados que describio +-- el cliente. El UNIQUE (id_cliente, id_experiencia, fecha_reserva) +-- lo bloquea. +-- INSERT INTO reservas (id_cliente, id_experiencia, id_instructor, fecha_reserva) VALUES (1, 1, 1, '2026-08-01'); diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-083/dml/operaciones.sql b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-083/dml/operaciones.sql new file mode 100644 index 00000000..443eb245 --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-083/dml/operaciones.sql @@ -0,0 +1,26 @@ +PRAGMA foreign_keys = ON; + +-- Ejercicio 083: Viajes y Paracaidismo +-- Operaciones de mantenimiento sobre los datos base. + +-- 1 UPDATE de estado: se confirma que Cristina Barrios cancelo su +-- reserva antes de saltar; el equipo de ventas se habia adelantado a +-- marcarla como realizada. +UPDATE reservas +SET estado = 'cancelada' +WHERE id_reserva = 4 AND estado = 'realizada'; + +-- 1 DELETE controlado: el pago de la reserva 4 quedo invalido apenas +-- se corrigio el estado (el salto nunca ocurrio). Solo se borran +-- pagos de reservas 'cancelada'; una reserva 'realizada' nunca pierde +-- su pago por este DELETE. +DELETE FROM pagos +WHERE id_reserva IN ( + SELECT id_reserva FROM reservas WHERE estado = 'cancelada' +); + +-- Caso que debe fallar / no ser recomendable (queda comentado): +-- borrar el pago de la reserva 1, que ya esta 'realizada' (resultado +-- oficial). El DELETE de arriba solo alcanza reservas 'cancelada' por +-- diseno. +-- DELETE FROM pagos WHERE id_reserva = 1; diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-083/dql/consultas.sql b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-083/dql/consultas.sql new file mode 100644 index 00000000..3b8855bb --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-083/dql/consultas.sql @@ -0,0 +1,39 @@ +.headers on +.mode column + +-- Ejercicio 083: Viajes y Paracaidismo +-- Consultas que responden preguntas reales del cliente. + +-- 1. Que registros principales existen: se usa la vista +-- vista_resumen_reservas (creada en ddl/schema.sql). +SELECT * +FROM vista_resumen_reservas; + +-- 2. Que reservas estan programadas, confirmadas, realizadas o +-- canceladas. +SELECT id_reserva, id_cliente, estado +FROM reservas +ORDER BY estado; + +-- 3. Que cliente tiene mas reservas (ranking de actividad). +SELECT c.nombre_cliente, COUNT(*) AS total_reservas +FROM clientes c +JOIN reservas r ON r.id_cliente = c.id_cliente +GROUP BY c.id_cliente, c.nombre_cliente +ORDER BY total_reservas DESC, c.nombre_cliente; + +-- 4. Reservas ordenadas por fecha. +SELECT id_reserva, fecha_reserva, estado +FROM reservas +ORDER BY fecha_reserva; + +-- 5. Reporte para decision de negocio: ingresos totales por tipo de +-- experiencia, para decidir en cual invertir mas promocion (GROUP BY +-- + HAVING, usando la vista para no repetir el JOIN). +SELECT tipo, + SUM(monto_pagado) AS ingresos_totales +FROM vista_resumen_reservas +WHERE monto_pagado IS NOT NULL +GROUP BY tipo +HAVING SUM(monto_pagado) > 0 +ORDER BY ingresos_totales DESC; diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-083/evidencias/resultados.md b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-083/evidencias/resultados.md new file mode 100644 index 00000000..73ce61d0 --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-083/evidencias/resultados.md @@ -0,0 +1,65 @@ +# Evidencias - Solicitudes SQL - Ejercicio 083 (Viajes y Paracaidismo) + +## 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-083.db < ddl/schema.sql +sqlite3 ejercicio-083.db < dml/inserts.sql +sqlite3 ejercicio-083.db < dml/operaciones.sql +sqlite3 ejercicio-083.db < dql/consultas.sql +``` + +## Resultados + +Datos base tras `dml/inserts.sql`: 4 clientes, 3 experiencias, 2 +instructores, 4 reservas (3 marcadas `realizada` en algun momento, 1 +`confirmada`) y 3 pagos. + +**Caso comentado verificado** (el problema central del cliente): + +- `INSERT INTO reservas (id_cliente, id_experiencia, ..., fecha_reserva) VALUES (1, 1, ..., '2026-08-01');` (repetir la reserva de Manuel Estrada) → `UNIQUE constraint failed: reservas.id_cliente, reservas.id_experiencia, reservas.fecha_reserva`. + +**1. Resumen de reservas via `vista_resumen_reservas`:** + +```text +id_reserva | nombre_cliente | nombre_experiencia | tipo | nombre_instructor | fecha_reserva | estado | monto_pagado +1 | Manuel Estrada | Salto en Tandem | paracaidismo | Hugo Marroquin | 2026-08-01 | realizada | 1800.0 +2 | Alejandra Chinchilla | Tour Volcan Pacaya | tour | Cristina Barrios Guia | 2026-08-02 | realizada | 450.0 +3 | Byron Xicay | Buceo en Arrecife | buceo | Cristina Barrios Guia | 2026-08-05 | confirmada | (NULL) +4 | Cristina Barrios | Salto en Tandem | paracaidismo | Cristina Barrios Guia | 2026-08-06 | cancelada | (NULL) +``` + +(La reserva 3 nunca tuvo pago y la 4 perdio el suyo al corregirse; la +vista muestra ambos casos con `monto_pagado = NULL` en vez de +ocultarlos, tal como pidio el cliente para tener reportes confiables.) + +**5. Ingresos totales por tipo de experiencia (para decidir en cual +invertir mas promocion):** + +```text +tipo ingresos_totales +paracaidismo 1800.0 +tour 450.0 +``` + +(Buceo no aparece: su unica reserva sigue sin pago.) + +## Operaciones de mantenimiento verificadas + +- `UPDATE reservas SET estado = 'cancelada' WHERE id_reserva = 4 ...;` → la reserva de Cristina Barrios se corrigio despues de confirmarse que cancelo antes de saltar. +- **DELETE controlado**: se elimino el pago que habia quedado invalido en la reserva 4, apenas se marco `cancelada`. Total de pagos: 3 -> 2. Ningun pago de una reserva `realizada` se toco. + +## Aprendizaje + +El `UNIQUE (id_cliente, id_experiencia, fecha_reserva)` en `reservas` +y el `UNIQUE (id_reserva)` en `pagos` resuelven directamente el +problema que trajo el cliente: registros incompletos o duplicados que +arruinan los reportes. La vista `vista_resumen_reservas`, con +`LEFT JOIN` a `pagos`, hace visible con `NULL` cuando una reserva +todavia no tiene pago, en vez de ocultarla del reporte como haria un +`JOIN` normal: eso es justo lo que hace que un reporte sea confiable, +porque no esconde informacion incompleta, la muestra explicitamente.