From ae95d594cf2611f53598ed88c5eda6eed5cbbe3f Mon Sep 17 00:00:00 2001 From: MariaJoseMontepequeZet Date: Tue, 25 Aug 2026 06:45:43 -0600 Subject: [PATCH 1/2] feat(sql): resolver ejercicio 81 --- .../maria-montepeque/ejercicio-81/README.md | 78 +++++++++++++++++++ .../ejercicio-81/ddl/schema.sql | 28 +++++++ .../ejercicio-81/dml/inserts.sql | 22 ++++++ .../ejercicio-81/dql/consultas.sql | 53 +++++++++++++ .../ejercicio-81/evidencias/resultados.md | 62 +++++++++++++++ 5 files changed, 243 insertions(+) create mode 100644 resoluciones/maria-montepeque/ejercicio-81/README.md create mode 100644 resoluciones/maria-montepeque/ejercicio-81/ddl/schema.sql create mode 100644 resoluciones/maria-montepeque/ejercicio-81/dml/inserts.sql create mode 100644 resoluciones/maria-montepeque/ejercicio-81/dql/consultas.sql create mode 100644 resoluciones/maria-montepeque/ejercicio-81/evidencias/resultados.md diff --git a/resoluciones/maria-montepeque/ejercicio-81/README.md b/resoluciones/maria-montepeque/ejercicio-81/README.md new file mode 100644 index 00000000..83d18aef --- /dev/null +++ b/resoluciones/maria-montepeque/ejercicio-81/README.md @@ -0,0 +1,78 @@ +# Ejercicio 81: SELECT Nivel Intermedio + +**Nombre:** Maria Jose Montepeque +**Fecha:** 2026-08-24 + +## Tema central + +SELECT + +## Descripcion del problema + +Una cafeteria necesita responder preguntas mas elaboradas que "mostrar +todo": que productos estan por encima del precio promedio, o cuantos +clientes distintos compraron en un periodo, sin depender de calculos +manuales fuera de la base de datos. + +## Tablas y relaciones + +- `clientes`: catalogo de clientes. +- `productos`: catalogo de productos, con su precio. +- `ventas`: relaciona un cliente con un producto en una fecha. + `clientes` 1—N `ventas`; `productos` 1—N `ventas`. + +## Uso de SELECT + +En `dql/consultas.sql`: + +1. `JOIN` con alias y una expresion calculada (`subtotal`), igual que + en el nivel basico, pero ahora combinando tres columnas de dos + tablas distintas. +2. Subconsulta: `WHERE precio > (SELECT AVG(precio) FROM productos)` + calcula el precio promedio del catalogo una sola vez y lo usa para + filtrar los productos caros, sin tener que calcular el promedio a + mano fuera de SQL. +3. `COUNT(DISTINCT id_cliente)`: cuenta cuantos clientes unicos + compraron algo, sin que un cliente que compro varias veces se + cuente varias veces. +4. `WHERE`, `ORDER BY` y `GROUP BY` con `SUM`, para filtrar, ordenar y + resumir. + +## 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`, `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 id_producto FROM productos, ventas;` falla porque tanto +`productos` como `ventas` tienen una columna `id_producto`, y sin +calificarla con el alias de la tabla (`p.id_producto` o +`v.id_producto`), SQLite no sabe cual de las dos usar. Se valido con +Python (`sqlite3`): lanza `ambiguous column name: id_producto`. + +## 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, 4 productos, 5 ventas. Cappuccino y + Croissant son los unicos productos por encima del precio promedio + (16.25); 3 clientes distintos compraron algo. + +## Como ejecutar + +```bash +sqlite3 ejercicio-81.db < ddl/schema.sql +sqlite3 ejercicio-81.db < dml/inserts.sql +sqlite3 ejercicio-81.db < dql/consultas.sql +``` + +No suba archivos `.db`, `.sqlite` ni `.sqlite3`. diff --git a/resoluciones/maria-montepeque/ejercicio-81/ddl/schema.sql b/resoluciones/maria-montepeque/ejercicio-81/ddl/schema.sql new file mode 100644 index 00000000..dd95e8fe --- /dev/null +++ b/resoluciones/maria-montepeque/ejercicio-81/ddl/schema.sql @@ -0,0 +1,28 @@ +PRAGMA foreign_keys = ON; + +-- Ejercicio 81: SELECT Nivel Intermedio +-- Tema central: SELECT +-- 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) +); + +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-81/dml/inserts.sql b/resoluciones/maria-montepeque/ejercicio-81/dml/inserts.sql new file mode 100644 index 00000000..386a4f90 --- /dev/null +++ b/resoluciones/maria-montepeque/ejercicio-81/dml/inserts.sql @@ -0,0 +1,22 @@ +PRAGMA foreign_keys = ON; + +-- Ejercicio 81: SELECT Nivel Intermedio +-- Datos de prueba. + +INSERT INTO clientes (nombre_cliente, telefono) VALUES + ('Manuel Estrada', '5555-3001'), + ('Alejandra Chinchilla', '5555-3002'), + ('Byron Xicay', '5555-3003'); + +INSERT INTO productos (nombre_producto, precio) VALUES + ('Cafe Americano', 15.00), + ('Cappuccino', 20.00), + ('Te Chai', 12.00), + ('Croissant', 18.00); + +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, 1, 2, '2026-08-03'); diff --git a/resoluciones/maria-montepeque/ejercicio-81/dql/consultas.sql b/resoluciones/maria-montepeque/ejercicio-81/dql/consultas.sql new file mode 100644 index 00000000..01572505 --- /dev/null +++ b/resoluciones/maria-montepeque/ejercicio-81/dql/consultas.sql @@ -0,0 +1,53 @@ +.headers on +.mode column + +-- Ejercicio 81: SELECT Nivel Intermedio +-- Consultas de validacion. + +-- 1. Mostrar todos los datos principales: ventas con JOIN a cliente y +-- producto, alias descriptivos y una expresion calculada (subtotal). +SELECT v.id_venta, + c.nombre_cliente AS cliente, + p.nombre_producto AS producto, + v.cantidad, + p.precio, + (v.cantidad * p.precio) AS subtotal +FROM ventas v +JOIN clientes c ON c.id_cliente = v.id_cliente +JOIN productos p ON p.id_producto = v.id_producto; + +-- 2. Consulta con WHERE: ventas del 2026-08-01. +SELECT id_venta, id_cliente, id_producto, cantidad +FROM ventas +WHERE fecha_venta = '2026-08-01'; + +-- 3. Consulta con ORDER BY: productos ordenados por precio, de mayor +-- a menor. +SELECT nombre_producto, precio +FROM productos +ORDER BY precio DESC; + +-- 4. Conteo o resumen: cantidad total vendida por producto. +SELECT p.nombre_producto, SUM(v.cantidad) AS unidades_vendidas +FROM ventas v +JOIN productos p ON p.id_producto = v.id_producto +GROUP BY p.id_producto, p.nombre_producto; + +-- 5. Validacion especifica de SELECT (nivel intermedio: subconsulta y +-- DISTINCT). Productos con precio por encima del precio promedio de +-- todo el catalogo: la subconsulta calcula el promedio una sola vez y +-- el SELECT externo lo usa para filtrar. +SELECT nombre_producto, precio +FROM productos +WHERE precio > (SELECT AVG(precio) FROM productos); + +-- DISTINCT: cuantos clientes distintos compraron algo (sin contar +-- repetidos, aunque un cliente haya comprado varias veces). +SELECT COUNT(DISTINCT id_cliente) AS clientes_distintos +FROM ventas; + +-- Caso comentado que debe fallar (no ser recomendable), dejar +-- comentado: hacer JOIN entre dos tablas que comparten un nombre de +-- columna (id_producto) sin calificarlo con el alias de la tabla. +-- SQLite no sabe de cual tabla tomar la columna. +-- SELECT id_producto FROM productos, ventas; diff --git a/resoluciones/maria-montepeque/ejercicio-81/evidencias/resultados.md b/resoluciones/maria-montepeque/ejercicio-81/evidencias/resultados.md new file mode 100644 index 00000000..a4398c2d --- /dev/null +++ b/resoluciones/maria-montepeque/ejercicio-81/evidencias/resultados.md @@ -0,0 +1,62 @@ +# Evidencias - Ejercicio 81 + +## Tema + +SELECT + +## 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-81.db < ddl/schema.sql +sqlite3 ejercicio-81.db < dml/inserts.sql +sqlite3 ejercicio-81.db < dql/consultas.sql +``` + +## Resultados + +**4. Resumen: unidades vendidas por producto:** + +```text +nombre_producto unidades_vendidas +Cafe Americano 4 +Cappuccino 1 +Te Chai 3 +Croissant 1 +``` + +**5a. Productos con precio por encima del promedio del catalogo +(subconsulta, promedio = 16.25):** + +```text +nombre_producto precio +Cappuccino 20.0 +Croissant 18.0 +``` + +**5b. Clientes distintos que compraron algo (`COUNT(DISTINCT ...)`):** + +```text +clientes_distintos +3 +``` + +**Caso comentado verificado:** + +- `SELECT id_producto FROM productos, ventas;` → `ambiguous column name: id_producto` (ambas tablas tienen esa columna y ninguna esta calificada con su alias). + +## Aprendizaje + +Ademas de alias y expresiones calculadas (nivel basico), este +ejercicio de nivel intermedio demostro dos tecnicas mas de `SELECT`: +una subconsulta (`WHERE precio > (SELECT AVG(precio) FROM productos)`) +que calcula un valor de referencia una sola vez y lo usa para filtrar +el resultado externo, y `COUNT(DISTINCT ...)` para contar valores +unicos sin que las repeticiones inflen el conteo. El caso comentado +muestra un error real y comun al combinar tablas: cuando dos tablas +comparten el nombre de una columna, `SELECT` no puede adivinar de cual +tabla tomarla si no se califica con el alias (`v.id_producto` o +`p.id_producto`). From c42510ef1ff55ef0f1ab351d78e4f836984d91ea Mon Sep 17 00:00:00 2001 From: MariaJoseMontepequeZet Date: Tue, 25 Aug 2026 06:47:10 -0600 Subject: [PATCH 2/2] feat(sql): resolver solicitud SQL ejercicio 081 (renta autos de lujo) --- .../solicitudes-sql/ejercicio-081/README.md | 93 +++++++++++++++++++ .../ejercicio-081/analisis/requerimiento.md | 92 ++++++++++++++++++ .../ejercicio-081/ddl/schema.sql | 78 ++++++++++++++++ .../ejercicio-081/diagramas/.gitkeep | 0 .../ejercicio-081/diagramas/diagrama-er.svg | 62 +++++++++++++ .../ejercicio-081/dml/inserts.sql | 58 ++++++++++++ .../ejercicio-081/dml/operaciones.sql | 26 ++++++ .../ejercicio-081/dql/consultas.sql | 40 ++++++++ .../ejercicio-081/evidencias/resultados.md | 72 ++++++++++++++ 9 files changed, 521 insertions(+) create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-081/README.md create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-081/analisis/requerimiento.md create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-081/ddl/schema.sql create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-081/diagramas/.gitkeep create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-081/diagramas/diagrama-er.svg create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-081/dml/inserts.sql create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-081/dml/operaciones.sql create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-081/dql/consultas.sql create mode 100644 resoluciones/maria-montepeque/solicitudes-sql/ejercicio-081/evidencias/resultados.md diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-081/README.md b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-081/README.md new file mode 100644 index 00000000..842b459e --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-081/README.md @@ -0,0 +1,93 @@ +# Solicitud SQL - Ejercicio 081: Renta Autos de Lujo + +**Nombre:** Maria Jose Montepeque +**Fecha:** 2026-08-24 + +## Solicitud del cliente + +Una empresa alquila autos deportivos y necesita controlar reservas, +clientes y pagos. El cliente dice que hoy todo se maneja en hojas de +calculo y que varias personas duplican datos sin darse cuenta. 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 + +Es un nivel 5 (solicitud profesional): ademas del modelo relacional, +se pide interpretar ambiguedad, normalizar datos, documentar +decisiones de diseno y crear al menos una vista SQL. El problema +central del cliente (duplicados por varias personas) se resuelve con +restricciones `UNIQUE` en los puntos exactos donde ocurriria un +duplicado real: pagos e inspecciones. El detalle completo del +analisis, incluidas las decisiones de modelado y la ambiguedad que no +resolvio el cliente, esta en +[analisis/requerimiento.md](analisis/requerimiento.md). + +## Que tablas cree y por que + +- `clientes`: catalogo de clientes, con licencia unica. +- `vehiculos`: catalogo de autos disponibles, con su tarifa diaria. +- `reservas`: tabla transaccional, cada renta de un vehiculo. +- `pagos`: resultado de una reserva. `UNIQUE (id_reserva)` impide + registrar dos pagos para la misma reserva. +- `inspecciones`: detalle de cada reserva (entrega y devolucion). + `UNIQUE (id_reserva, tipo_inspeccion)` impide cargar dos veces la + misma inspeccion. + +## Vista SQL + +`vista_resumen_reservas` (definida en +[ddl/schema.sql](ddl/schema.sql)) junta reserva, cliente, vehiculo y +pago en una sola consulta, usando `LEFT JOIN` para que las reservas +sin pago todavia (en curso o canceladas) sigan apareciendo con +`monto_pagado = NULL` en vez de desaparecer del reporte. + +## Como se relacionan + +`clientes` 1:N `reservas`; `vehiculos` 1:N `reservas`; `reservas` 1:1 +`pagos`; `reservas` 1:N `inspecciones` (maximo 2: entrega y +devolucion). El diagrama esta en +[diagramas/diagrama-er.svg](diagramas/diagrama-er.svg). + +## Que datos de prueba use + +4 clientes, 4 vehiculos, 4 reservas (2 `finalizada` con pago, 1 +`en_curso` sin pago, 1 `reservada` con una inspeccion cargada por +error), 2 pagos y 6 inspecciones. Tambien un `INSERT` comentado que +reproduce exactamente el problema del cliente (segundo pago para la +misma 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 cancelada antes de recoger el vehiculo) y un `DELETE` +controlado que elimina la inspeccion de entrega que quedo invalida, +sin tocar ninguna inspeccion de una reserva ya `en_curso` o +`finalizada`. + +## 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 +categoria de vehiculo, para decidir en cual invertir mas flota. + +## 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-081.db < ddl/schema.sql +sqlite3 ejercicio-081.db < dml/inserts.sql +sqlite3 ejercicio-081.db < dml/operaciones.sql +sqlite3 ejercicio-081.db < dql/consultas.sql +``` + +No suba archivos `.db`, `.sqlite`, `.sqlite3` ni `.dump`. diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-081/analisis/requerimiento.md b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-081/analisis/requerimiento.md new file mode 100644 index 00000000..a73e740a --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-081/analisis/requerimiento.md @@ -0,0 +1,92 @@ +# Analisis del requerimiento - Ejercicio 081 + +## Solicitud entendida + +Una empresa alquila autos deportivos y de lujo, y necesita controlar +reservas, clientes y pagos. Hoy todo se maneja en hojas de calculo y +varias personas duplican datos sin darse cuenta: el mismo pago +registrado dos veces, o la misma inspeccion de entrega cargada por +error mas de una vez. 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 | +| --- | --- | --- | +| clientes | Catalogo: cada cliente que renta un vehiculo | nombre_cliente, licencia (unica), telefono (unico) | +| vehiculos | Catalogo: cada auto disponible para renta | modelo, placa (unica), categoria, tarifa_diaria | +| reservas | Tabla transaccional: cada renta de un vehiculo por un cliente | fecha_inicio, fecha_fin, estado | +| pagos | Resultado: el pago de una reserva, uno por reserva | monto, metodo_pago | +| inspecciones | Detalle de cada reserva: revision del vehiculo al entregarlo y al devolverlo | tipo_inspeccion, estado_vehiculo | + +## Relaciones detectadas + +| Relacion | Tipo | Explicacion | +| --- | --- | --- | +| clientes -> reservas | 1:N | Un cliente puede tener varias reservas. | +| vehiculos -> reservas | 1:N | Un vehiculo se renta en muchas reservas distintas a lo largo del tiempo. | +| reservas -> pagos | 1:1 | Cada reserva tiene, como mucho, un pago oficial. | +| reservas -> inspecciones | 1:N (maximo 2) | Una reserva tiene una inspeccion de entrega y, cuando corresponde, una de devolucion. | + +## Decisiones de modelado y ambiguedad interpretada + +- **Duplicados (el problema central del cliente):** se traduce en dos + restricciones concretas: `pagos.id_reserva` es `UNIQUE` (una reserva + no puede tener dos pagos, que era justo el tipo de error que + describio el cliente en la hoja de calculo), e + `inspecciones` tiene `UNIQUE (id_reserva, tipo_inspeccion)` (no se + puede cargar dos veces la inspeccion de entrega de la misma + reserva). +- **Normalizacion:** `vehiculos.tarifa_diaria` vive en `vehiculos`, no + en `reservas`, porque es un dato del catalogo, no de la transaccion; + el monto real cobrado (que puede variar por descuentos, por + ejemplo) se guarda aparte en `pagos.monto`. +- **Ambiguedad no resuelta por el cliente:** no se detallo si se debe + validar que un vehiculo no tenga dos reservas con fechas + encimadas (traslape). Se documenta como una regla de negocio + deseable pero fuera del alcance de este nivel: SQLite no permite un + `CHECK` que compare filas distintas de la misma tabla sin un + `TRIGGER`, asi que esa validacion quedaria para el proceso de + reserva (aplicacion), no para la base de datos en si. +- **Vista SQL:** se crea `vista_resumen_reservas`, que junta reserva, + cliente, vehiculo y pago (si existe) en una sola consulta legible. + Resuelve la necesidad del cliente de "consultar datos" sin tener que + repetir el mismo `JOIN` de cuatro tablas cada vez. + +## Reglas de negocio + +- Regla 1 (relaciones invalidas): toda reserva debe apuntar a un + cliente y a un vehiculo reales; todo pago y toda inspeccion deben + apuntar a una reserva real (`FOREIGN KEY` en cadena). +- Regla 2 (registros repetidos): `clientes.licencia`, + `clientes.telefono` y `vehiculos.placa` no se repiten (`UNIQUE`); + ver duplicados arriba. +- Regla 3 (valores fuera de rango): `vehiculos.tarifa_diaria` y + `pagos.monto` nunca negativos; `reservas.fecha_fin` siempre + posterior a `reservas.fecha_inicio` (`CHECK`). +- Regla 4: una reserva nace `'reservada'` y avanza a `'en_curso'`, + `'finalizada'` o `'cancelada'` (`CHECK`); se corrige con `UPDATE`. +- Regla 5: una inspeccion solo tiene sentido si la reserva sigue + vigente. Si una reserva se cancela y ya tenia una inspeccion de + entrega cargada por error (por ejemplo, antes de que el cliente + llegara), esa fila se elimina; nunca se borra una inspeccion de una + reserva `'en_curso'` o `'finalizada'`, porque ya es parte del + historico oficial. + +## Supuestos + +- Se asume que un cliente puede tener varias reservas activas a la + vez (no se limita a una reserva por cliente). +- No se detallo penalizacion por cancelacion; se asume que una + reserva `'cancelada'` simplemente no genera pago. + +## Preguntas que responde la base de datos + +1. Que reservas existen, con que cliente, que vehiculo y su pago (via + la vista `vista_resumen_reservas`). +2. Que reservas estan reservadas, en curso, finalizadas o canceladas. +3. Que cliente tiene mas reservas (ranking de actividad). +4. Como se ordenan las reservas por fecha de inicio. +5. Que categoria de vehiculo genero mas ingresos, para decidir en cual + invertir mas flota. diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-081/ddl/schema.sql b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-081/ddl/schema.sql new file mode 100644 index 00000000..f30c8ec6 --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-081/ddl/schema.sql @@ -0,0 +1,78 @@ +PRAGMA foreign_keys = ON; + +-- Ejercicio 081: Renta Autos de Lujo +-- Modelo: clientes -> reservas (1:N); vehiculos -> reservas (1:N); +-- reservas -> pagos (1:1); reservas -> inspecciones (1:N, maximo 2: +-- entrega y devolucion). + +CREATE TABLE clientes ( + id_cliente INTEGER PRIMARY KEY AUTOINCREMENT, + nombre_cliente TEXT NOT NULL, + licencia TEXT NOT NULL UNIQUE, + telefono TEXT NOT NULL UNIQUE +); + +CREATE TABLE vehiculos ( + id_vehiculo INTEGER PRIMARY KEY AUTOINCREMENT, + modelo TEXT NOT NULL, + placa TEXT NOT NULL UNIQUE, + categoria TEXT NOT NULL CHECK (categoria IN ('deportivo', 'lujo', 'convertible')), + tarifa_diaria REAL NOT NULL CHECK (tarifa_diaria >= 0) +); + +CREATE TABLE reservas ( + id_reserva INTEGER PRIMARY KEY AUTOINCREMENT, + id_cliente INTEGER NOT NULL, + id_vehiculo INTEGER NOT NULL, + fecha_inicio TEXT NOT NULL, + fecha_fin TEXT NOT NULL, + estado TEXT NOT NULL DEFAULT 'reservada' + CHECK (estado IN ('reservada', 'en_curso', 'finalizada', 'cancelada')), + + FOREIGN KEY (id_cliente) REFERENCES clientes (id_cliente), + FOREIGN KEY (id_vehiculo) REFERENCES vehiculos (id_vehiculo), + CHECK (fecha_fin > fecha_inicio) +); + +-- pagos: el UNIQUE sobre id_reserva ataca directamente el problema +-- que describio el cliente (pagos duplicados en la hoja de calculo). +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) +); + +-- inspecciones: el UNIQUE compuesto impide cargar dos veces la misma +-- inspeccion (entrega o devolucion) para la misma reserva. +CREATE TABLE inspecciones ( + id_inspeccion INTEGER PRIMARY KEY AUTOINCREMENT, + id_reserva INTEGER NOT NULL, + tipo_inspeccion TEXT NOT NULL CHECK (tipo_inspeccion IN ('entrega', 'devolucion')), + estado_vehiculo TEXT NOT NULL, + fecha_inspeccion TEXT NOT NULL DEFAULT (datetime('now')), + + FOREIGN KEY (id_reserva) REFERENCES reservas (id_reserva), + UNIQUE (id_reserva, tipo_inspeccion) +); + +-- Vista SQL (requerida en nivel 5): resumen legible de cada reserva +-- con su cliente, su vehiculo y su pago (si ya existe). Evita repetir +-- el mismo JOIN de 4 tablas cada vez que se necesita este reporte. +CREATE VIEW vista_resumen_reservas AS + SELECT + r.id_reserva, + c.nombre_cliente, + v.modelo, + v.categoria, + r.fecha_inicio, + r.fecha_fin, + r.estado, + p.monto AS monto_pagado + FROM reservas r + JOIN clientes c ON c.id_cliente = r.id_cliente + JOIN vehiculos v ON v.id_vehiculo = r.id_vehiculo + LEFT JOIN pagos p ON p.id_reserva = r.id_reserva; diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-081/diagramas/.gitkeep b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-081/diagramas/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-081/diagramas/diagrama-er.svg b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-081/diagramas/diagrama-er.svg new file mode 100644 index 00000000..d75676af --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-081/diagramas/diagrama-er.svg @@ -0,0 +1,62 @@ + + + + + clientes + id_cliente PK + licencia UNIQUE + telefono UNIQUE + + + vehiculos + id_vehiculo PK + placa UNIQUE + categoria, tarifa_diaria + + + reservas + id_reserva PK + id_cliente FK, id_vehiculo FK + fecha_inicio, fecha_fin + estado CHECK + + + pagos + id_pago PK + id_reserva FK UNIQUE (1:1) + monto, metodo_pago + + + inspecciones + id_inspeccion PK + id_reserva FK + UNIQUE(reserva, tipo) + + + vista_resumen_reservas + JOIN de las 4 tablas + + + + 1:N + + + + 1:N + + + + 1:1 + + + + 1:N + diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-081/dml/inserts.sql b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-081/dml/inserts.sql new file mode 100644 index 00000000..fab75061 --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-081/dml/inserts.sql @@ -0,0 +1,58 @@ +PRAGMA foreign_keys = ON; + +-- Ejercicio 081: Renta Autos de Lujo +-- Datos base: 4 clientes, 4 vehiculos, 4 reservas (2 finalizadas con +-- pago, 1 en curso sin pago todavia, 1 reservada que se cancela con +-- una inspeccion cargada por error) y sus inspecciones/pagos. + +INSERT INTO clientes (nombre_cliente, licencia, telefono) VALUES + ('Manuel Estrada', 'LIC-2001', '5555-7001'), + ('Alejandra Chinchilla', 'LIC-2002', '5555-7002'), + ('Byron Xicay', 'LIC-2003', '5555-7003'), + ('Cristina Barrios', 'LIC-2004', '5555-7004'); + +INSERT INTO vehiculos (modelo, placa, categoria, tarifa_diaria) VALUES + ('Ferrari 488', 'LUX-001', 'deportivo', 1200.00), + ('Lamborghini Huracan', 'LUX-002', 'deportivo', 1500.00), + ('Rolls Royce Phantom', 'LUX-003', 'lujo', 2000.00), + ('Mustang Convertible', 'LUX-004', 'convertible', 800.00); + +-- Reserva 1: Manuel, Ferrari 488, 2 dias, finalizada. +INSERT INTO reservas (id_cliente, id_vehiculo, fecha_inicio, fecha_fin, estado) VALUES + (1, 1, '2026-08-01', '2026-08-03', 'finalizada'); +INSERT INTO inspecciones (id_reserva, tipo_inspeccion, estado_vehiculo) VALUES + (1, 'entrega', 'Sin danos, tanque lleno'), + (1, 'devolucion', 'Sin danos, tanque lleno'); +INSERT INTO pagos (id_reserva, monto, metodo_pago) VALUES + (1, 2400.00, 'tarjeta'); + +-- Reserva 2: Alejandra, Rolls Royce Phantom, 3 dias, finalizada. +INSERT INTO reservas (id_cliente, id_vehiculo, fecha_inicio, fecha_fin, estado) VALUES + (2, 3, '2026-08-02', '2026-08-05', 'finalizada'); +INSERT INTO inspecciones (id_reserva, tipo_inspeccion, estado_vehiculo) VALUES + (2, 'entrega', 'Sin danos'), + (2, 'devolucion', 'Rayon leve en puerta trasera'); +INSERT INTO pagos (id_reserva, monto, metodo_pago) VALUES + (2, 6000.00, 'transferencia'); + +-- Reserva 3: Byron, Lamborghini Huracan, 2 dias, en curso (todavia no +-- se devuelve, sin pago registrado todavia). +INSERT INTO reservas (id_cliente, id_vehiculo, fecha_inicio, fecha_fin, estado) VALUES + (3, 2, '2026-08-06', '2026-08-08', 'en_curso'); +INSERT INTO inspecciones (id_reserva, tipo_inspeccion, estado_vehiculo) VALUES + (3, 'entrega', 'Sin danos, tanque lleno'); + +-- Reserva 4: Cristina, Mustang Convertible, todavia 'reservada'. Se +-- registro por error una inspeccion de entrega antes de que la +-- clienta llegara; ella cancelo la reserva antes de recoger el +-- vehiculo. Se corrige en dml/operaciones.sql. +INSERT INTO reservas (id_cliente, id_vehiculo, fecha_inicio, fecha_fin, estado) VALUES + (4, 4, '2026-08-04', '2026-08-05', 'reservada'); +INSERT INTO inspecciones (id_reserva, tipo_inspeccion, estado_vehiculo) VALUES + (4, 'entrega', 'Sin danos, tanque lleno'); + +-- Caso comentado que debe fallar (queda comentado): registrar un +-- segundo pago para la reserva 1, exactamente el problema que +-- describio el cliente (pagos duplicados en la hoja de calculo). El +-- UNIQUE (id_reserva) en pagos lo bloquea. +-- INSERT INTO pagos (id_reserva, monto, metodo_pago) VALUES (1, 2400.00, 'tarjeta'); diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-081/dml/operaciones.sql b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-081/dml/operaciones.sql new file mode 100644 index 00000000..dd7f49c6 --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-081/dml/operaciones.sql @@ -0,0 +1,26 @@ +PRAGMA foreign_keys = ON; + +-- Ejercicio 081: Renta Autos de Lujo +-- Operaciones de mantenimiento sobre los datos base. + +-- 1 UPDATE de estado: Cristina cancela su reserva antes de recoger el +-- vehiculo. +UPDATE reservas +SET estado = 'cancelada' +WHERE id_reserva = 4 AND estado = 'reservada'; + +-- 1 DELETE controlado: la inspeccion de entrega de la reserva 4 +-- quedo invalida apenas se cancelo (el vehiculo nunca se entrego de +-- verdad). Solo se borran inspecciones de reservas 'cancelada'; una +-- reserva 'en_curso' o 'finalizada' nunca pierde sus inspecciones por +-- este DELETE. +DELETE FROM inspecciones +WHERE id_reserva IN ( + SELECT id_reserva FROM reservas WHERE estado = 'cancelada' +); + +-- Caso que debe fallar / no ser recomendable (queda comentado): +-- borrar una inspeccion de la reserva 1, que ya esta 'finalizada' +-- (parte del historico oficial). El DELETE de arriba solo alcanza +-- reservas 'cancelada' por diseno. +-- DELETE FROM inspecciones WHERE id_reserva = 1 AND tipo_inspeccion = 'entrega'; diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-081/dql/consultas.sql b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-081/dql/consultas.sql new file mode 100644 index 00000000..a87e4181 --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-081/dql/consultas.sql @@ -0,0 +1,40 @@ +.headers on +.mode column + +-- Ejercicio 081: Renta Autos de Lujo +-- Consultas que responden preguntas reales del cliente. + +-- 1. Que registros principales existen: se usa la vista +-- vista_resumen_reservas (creada en ddl/schema.sql) en vez de repetir +-- el JOIN de 4 tablas. +SELECT * +FROM vista_resumen_reservas; + +-- 2. Que reservas estan reservadas, en curso, finalizadas 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 de inicio. +SELECT id_reserva, fecha_inicio, fecha_fin, estado +FROM reservas +ORDER BY fecha_inicio; + +-- 5. Reporte para decision de negocio: ingresos totales por +-- categoria de vehiculo, para decidir en cual invertir mas flota +-- (GROUP BY + HAVING, usando la vista para no repetir el JOIN). +SELECT categoria, + SUM(monto_pagado) AS ingresos_totales +FROM vista_resumen_reservas +WHERE monto_pagado IS NOT NULL +GROUP BY categoria +HAVING SUM(monto_pagado) > 0 +ORDER BY ingresos_totales DESC; diff --git a/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-081/evidencias/resultados.md b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-081/evidencias/resultados.md new file mode 100644 index 00000000..eda41469 --- /dev/null +++ b/resoluciones/maria-montepeque/solicitudes-sql/ejercicio-081/evidencias/resultados.md @@ -0,0 +1,72 @@ +# Evidencias - Solicitudes SQL - Ejercicio 081 (Renta Autos de Lujo) + +## 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-081.db < ddl/schema.sql +sqlite3 ejercicio-081.db < dml/inserts.sql +sqlite3 ejercicio-081.db < dml/operaciones.sql +sqlite3 ejercicio-081.db < dql/consultas.sql +``` + +## Resultados + +Datos base tras `dml/inserts.sql`: 4 clientes, 4 vehiculos, 4 reservas +(2 `finalizada` con pago, 1 `en_curso` sin pago, 1 `reservada` con una +inspeccion cargada por error), 2 pagos y 6 inspecciones. + +**Caso comentado verificado** (el problema central del cliente): + +- `INSERT INTO pagos (id_reserva, ...) VALUES (1, ...);` (segundo pago para la reserva 1) → `UNIQUE constraint failed: pagos.id_reserva`. + +**1. Resumen de reservas via `vista_resumen_reservas`:** + +```text +id_reserva | nombre_cliente | modelo | categoria | fecha_inicio | fecha_fin | estado | monto_pagado +1 | Manuel Estrada | Ferrari 488 | deportivo | 2026-08-01 | 2026-08-03 | finalizada | 2400.0 +2 | Alejandra Chinchilla | Rolls Royce Phantom | lujo | 2026-08-02 | 2026-08-05 | finalizada | 6000.0 +3 | Byron Xicay | Lamborghini Huracan | deportivo | 2026-08-06 | 2026-08-08 | en_curso | (NULL) +4 | Cristina Barrios | Mustang Convertible | convertible | 2026-08-04 | 2026-08-05 | cancelada | (NULL) +``` + +(La vista usa `LEFT JOIN` con `pagos`, por eso las reservas 3 y 4, que +todavia no tienen pago, aparecen con `monto_pagado = NULL` en vez de +desaparecer del resultado.) + +**3. Cliente con mas reservas:** los 4 clientes tienen exactamente 1 +reserva cada uno. + +**5. Ingresos totales por categoria de vehiculo (para decidir en +cual invertir mas flota):** + +```text +categoria ingresos_totales +lujo 6000.0 +deportivo 2400.0 +``` + +(Convertible no aparece: su unica reserva se cancelo y nunca genero +pago.) + +## Operaciones de mantenimiento verificadas + +- `UPDATE reservas SET estado = 'cancelada' WHERE id_reserva = 4 ...;` → la reserva del Mustang Convertible se cancelo antes de que Cristina recogiera el vehiculo. +- **DELETE controlado**: se elimino la unica inspeccion de entrega que habia quedado invalida en la reserva 4, apenas se marco `cancelada`. Total de inspecciones: 6 -> 5. Ninguna inspeccion de una reserva `en_curso` o `finalizada` se toco. + +## Aprendizaje + +El `UNIQUE (id_reserva)` en `pagos` y el `UNIQUE (id_reserva, tipo_inspeccion)` en `inspecciones` resuelven directamente el problema que +trajo el cliente: datos duplicados por varias personas trabajando +sobre la misma hoja de calculo. La vista `vista_resumen_reservas` +demuestra la habilidad de nivel 5 de "crear vistas": centraliza el +`JOIN` de 4 tablas en un solo objeto reutilizable, y con `LEFT JOIN` +maneja correctamente el caso ambiguo de una reserva que todavia no +tiene pago, sin que desaparezca del reporte. La decision de no +validar traslape de fechas entre reservas del mismo vehiculo con un +`CHECK` (SQLite no lo permite sin `TRIGGER`) se documento +explicitamente en el analisis en vez de dejarla como un vacio sin +explicar.