Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions resoluciones/maria-montepeque/ejercicio-81/README.md
Original file line number Diff line number Diff line change
@@ -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`.
28 changes: 28 additions & 0 deletions resoluciones/maria-montepeque/ejercicio-81/ddl/schema.sql
Original file line number Diff line number Diff line change
@@ -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)
);
22 changes: 22 additions & 0 deletions resoluciones/maria-montepeque/ejercicio-81/dml/inserts.sql
Original file line number Diff line number Diff line change
@@ -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');
53 changes: 53 additions & 0 deletions resoluciones/maria-montepeque/ejercicio-81/dql/consultas.sql
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -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`).
Original file line number Diff line number Diff line change
@@ -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`.
Loading
Loading