Skip to content
Merged
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
84 changes: 84 additions & 0 deletions resoluciones/maria-montepeque/ejercicio-73/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Ejercicio 73: INSERT Nivel Aplicado

**Nombre:** Maria Jose Montepeque
**Fecha:** 2026-08-24

## Tema central

INSERT

## Descripcion del problema

Una bodega de dispositivos tecnologicos necesita llevar el inventario
de sus productos sin depender de un numero de stock que alguien tenga
que actualizar a mano. En vez de eso, cada entrada y cada salida de
bodega se registra como un movimiento, y el stock real de cualquier
producto se calcula sumando sus entradas y restando sus salidas: un
caso de negocio completo, con reporte final, propio del nivel
aplicado.

## Tablas y relaciones

- `categorias`: catalogo de categorias de producto.
- `productos`: catalogo de productos, cada uno de una categoria.
- `movimientos`: historial de entradas y salidas de bodega.
`categorias` 1—N `productos`; `productos` 1—N `movimientos`.

## Uso de INSERT

En `dml/inserts.sql`:

1. `INSERT` de una sola fila: se registra la primera categoria.
2. `INSERT` multiple (`VALUES (...), (...)`): el resto de categorias,
los 5 productos, las 5 entradas iniciales de bodega y las 5
salidas por ventas o uso interno.
3. `INSERT` omitiendo `tipo_movimiento`: el reabastecimiento de
Laptop Pro 14 se inserta sin indicar el tipo, y queda en su
`DEFAULT` (`'entrada'`).

La consulta 5 en `dql/consultas.sql` es el reporte final del caso de
negocio: reconstruye el stock de cada producto solo a partir de los
`INSERT` de `movimientos`, sin que exista ninguna columna de stock
guardada aparte. Esto confirma que todos los `INSERT` cumplieron su
proposito.

## Otras restricciones aplicadas

- `PRIMARY KEY` autoincremental en las 3 tablas.
- `FOREIGN KEY`: `productos.id_categoria`, `movimientos.id_producto`.
- `NOT NULL` en todas las columnas obligatorias.
- `UNIQUE`: `categorias.nombre_categoria`, `productos.nombre_producto`.
- `CHECK`: `productos.precio_unitario >= 0`,
`movimientos.tipo_movimiento IN (...)`, `movimientos.cantidad > 0`.
- `DEFAULT` en `movimientos.tipo_movimiento` y `fecha_movimiento`.
- `PRAGMA foreign_keys = ON;` activado al inicio del script.

## Casos que fallan / no recomendables (comentados en `dml/inserts.sql`)

Uno por cada restriccion, validado con Python (`sqlite3`):

- Repetir `nombre_producto` -> `UNIQUE constraint failed`.
- Apuntar a un `id_producto` que no existe -> `FOREIGN KEY constraint failed`.
- Registrar una `cantidad` negativa -> `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: 3 categorias, 5 productos, 11 movimientos (6
entradas, 5 salidas). Stock final verificado: Laptop Pro 14 = 12,
Laptop Air 13 = 6, Mouse Inalambrico = 38, Teclado Mecanico = 23,
Disco SSD 1TB = 16.

## Como ejecutar

```bash
sqlite3 ejercicio-73.db < ddl/schema.sql
sqlite3 ejercicio-73.db < dml/inserts.sql
sqlite3 ejercicio-73.db < dql/consultas.sql
```

No suba archivos `.db`, `.sqlite` ni `.sqlite3`.
34 changes: 34 additions & 0 deletions resoluciones/maria-montepeque/ejercicio-73/ddl/schema.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
PRAGMA foreign_keys = ON;

-- Ejercicio 73: INSERT Nivel Aplicado
-- Tema central: INSERT
-- Contexto: inventario de dispositivos tecnologicos en bodega.

CREATE TABLE categorias (
id_categoria INTEGER PRIMARY KEY AUTOINCREMENT,
nombre_categoria TEXT NOT NULL UNIQUE
);

CREATE TABLE productos (
id_producto INTEGER PRIMARY KEY AUTOINCREMENT,
nombre_producto TEXT NOT NULL UNIQUE,
id_categoria INTEGER NOT NULL,
precio_unitario REAL NOT NULL CHECK (precio_unitario >= 0),

FOREIGN KEY (id_categoria) REFERENCES categorias (id_categoria)
);

-- movimientos: el stock de cada producto no se guarda como columna
-- aparte, se calcula a partir de este historial de entradas y
-- salidas (ver consulta 5 en dql/consultas.sql, el caso de negocio
-- con reporte final propio del nivel aplicado).
CREATE TABLE movimientos (
id_movimiento INTEGER PRIMARY KEY AUTOINCREMENT,
id_producto INTEGER NOT NULL,
tipo_movimiento TEXT NOT NULL DEFAULT 'entrada'
CHECK (tipo_movimiento IN ('entrada', 'salida')),
cantidad INTEGER NOT NULL CHECK (cantidad > 0),
fecha_movimiento TEXT NOT NULL DEFAULT (datetime('now')),

FOREIGN KEY (id_producto) REFERENCES productos (id_producto)
);
56 changes: 56 additions & 0 deletions resoluciones/maria-montepeque/ejercicio-73/dml/inserts.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
PRAGMA foreign_keys = ON;

-- Ejercicio 73: INSERT Nivel Aplicado
-- Datos de prueba para validar el tema INSERT.

-- 1. INSERT de una sola fila: se registra la primera categoria.
INSERT INTO categorias (nombre_categoria) VALUES
('Laptops');

-- 2. INSERT multiple: el resto de categorias.
INSERT INTO categorias (nombre_categoria) VALUES
('Perifericos'),
('Almacenamiento');

-- 3. INSERT multiple de productos, con todas las columnas explicitas.
INSERT INTO productos (nombre_producto, id_categoria, precio_unitario) VALUES
('Laptop Pro 14', 1, 8500.00),
('Laptop Air 13', 1, 6200.00),
('Mouse Inalambrico', 2, 150.00),
('Teclado Mecanico', 2, 320.00),
('Disco SSD 1TB', 3, 480.00);

-- 4. INSERT multiple: entradas iniciales de bodega (una por
-- producto), con tipo_movimiento explicito.
INSERT INTO movimientos (id_producto, tipo_movimiento, cantidad) VALUES
(1, 'entrada', 10),
(2, 'entrada', 8),
(3, 'entrada', 50),
(4, 'entrada', 30),
(5, 'entrada', 20);

-- 5. INSERT multiple: salidas por ventas o uso interno.
INSERT INTO movimientos (id_producto, tipo_movimiento, cantidad) VALUES
(1, 'salida', 3),
(3, 'salida', 12),
(4, 'salida', 7),
(5, 'salida', 4),
(2, 'salida', 2);

-- 6. INSERT SIN indicar tipo_movimiento: se omite a proposito para
-- que quede en su DEFAULT ('entrada'). Reabastecimiento de laptops
-- Pro 14 despues de la salida anterior.
INSERT INTO movimientos (id_producto, cantidad) VALUES
(1, 5);

-- Casos comentados que deben fallar (no ser recomendables), dejar
-- comentados:

-- 1) Registro repetido: nombre_producto ya existe, viola el UNIQUE.
-- INSERT INTO productos (nombre_producto, id_categoria, precio_unitario) VALUES ('Laptop Pro 14', 1, 9000.00);

-- 2) Relacion invalida: id_producto = 99 no existe, viola el FOREIGN KEY.
-- INSERT INTO movimientos (id_producto, tipo_movimiento, cantidad) VALUES (99, 'entrada', 5);

-- 3) Valor fuera de rango: cantidad negativa, viola el CHECK.
-- INSERT INTO movimientos (id_producto, tipo_movimiento, cantidad) VALUES (2, 'entrada', -10);
50 changes: 50 additions & 0 deletions resoluciones/maria-montepeque/ejercicio-73/dql/consultas.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
.headers on
.mode column

-- Ejercicio 73: INSERT Nivel Aplicado
-- Consultas de validacion.

-- 1. Mostrar todos los datos principales (movimientos con producto y
-- categoria).
SELECT m.id_movimiento,
p.nombre_producto,
c.nombre_categoria,
m.tipo_movimiento,
m.cantidad,
m.fecha_movimiento
FROM movimientos m
JOIN productos p ON p.id_producto = m.id_producto
JOIN categorias c ON c.id_categoria = p.id_categoria;

-- 2. Consulta con WHERE: solo las salidas de bodega.
SELECT id_movimiento, id_producto, cantidad
FROM movimientos
WHERE tipo_movimiento = 'salida';

-- 3. Consulta con ORDER BY: movimientos ordenados por fecha.
SELECT id_movimiento, fecha_movimiento, tipo_movimiento, cantidad
FROM movimientos
ORDER BY fecha_movimiento;

-- 4. Conteo o resumen: total de movimientos por tipo.
SELECT tipo_movimiento, COUNT(*) AS total
FROM movimientos
GROUP BY tipo_movimiento;

-- 5. Caso de negocio con reporte final (nivel aplicado): el stock
-- real de cada producto no se guardo en ninguna columna, se calcula
-- sumando todas las entradas y restando todas las salidas. Esta
-- consulta demuestra que los INSERT de movimientos cumplieron su
-- proposito: reconstruyen el stock actual desde cero, solo con el
-- historial.
SELECT p.nombre_producto,
SUM(
CASE
WHEN m.tipo_movimiento = 'entrada' THEN m.cantidad
ELSE -m.cantidad
END
) AS stock_actual
FROM productos p
JOIN movimientos m ON m.id_producto = p.id_producto
GROUP BY p.id_producto, p.nombre_producto
ORDER BY p.nombre_producto;
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Evidencias - Ejercicio 73

## Tema

INSERT

## 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-73.db < ddl/schema.sql
sqlite3 ejercicio-73.db < dml/inserts.sql
sqlite3 ejercicio-73.db < dql/consultas.sql
```

## Resultados

Datos base tras `dml/inserts.sql`: 3 categorias, 5 productos y 11
movimientos (6 entradas, 5 salidas).

**Casos comentados verificados** (descomentados y ejecutados por
separado para confirmar que cada uno falla):

- `INSERT INTO productos (nombre_producto, ...) VALUES ('Laptop Pro 14', ...);` → `UNIQUE constraint failed: productos.nombre_producto`.
- `INSERT INTO movimientos (id_producto, ...) VALUES (99, ...);` → `FOREIGN KEY constraint failed`.
- `INSERT INTO movimientos (..., cantidad) VALUES (..., -10);` → `CHECK constraint failed: cantidad > 0`.

**4. Resumen: movimientos por tipo:**

```text
tipo_movimiento total
entrada 6
salida 5
```

**5. Caso de negocio con reporte final: stock real de cada producto,
calculado sumando entradas y restando salidas (sin ninguna columna de
stock guardada):**

```text
nombre_producto stock_actual
Disco SSD 1TB 16
Laptop Air 13 6
Laptop Pro 14 12
Mouse Inalambrico 38
Teclado Mecanico 23
```

Verificacion manual de Laptop Pro 14: entrada 10, salida 3, entrada 5
(sin indicar `tipo_movimiento`, quedo en `'entrada'` por `DEFAULT`) =
10 - 3 + 5 = 12. Coincide exactamente con el reporte.

## Aprendizaje

Ademas de `INSERT` de una fila, `INSERT` multiple y `INSERT` omitiendo
una columna con `DEFAULT` (vistos en los niveles basico e
intermedio), este ejercicio de nivel aplicado demostro un caso de
negocio completo: el stock de cada producto nunca se guarda como un
numero fijo que hay que mantener sincronizado a mano, se reconstruye
siempre desde el historial de `movimientos` con una consulta de
reporte. Esto significa que cada `INSERT` en `movimientos` es, en si
mismo, la unica fuente de verdad del inventario: si los `INSERT` estan
completos y correctos (protegidos por `CHECK` y `FOREIGN KEY`), el
reporte final siempre sera correcto sin necesidad de ningun `UPDATE`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Solicitud SQL - Ejercicio 073: Clanes Shooter

**Nombre:** Maria Jose Montepeque
**Fecha:** 2026-08-24

## Solicitud del cliente

Una plataforma de shooter administra clanes, scrims, mapas y
resultados. 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 central no es "guardar resultados", es garantizar que
cada scrim tenga como maximo un resultado oficial: sin eso, cualquier
reporte de victorias o de actividad queda contaminado por registros
duplicados o incompletos. 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

- `clanes`: catalogo de clanes registrados.
- `jugadores`: catalogo de jugadores, cada uno miembro de un clan.
- `mapas`: catalogo de mapas disponibles para scrims.
- `scrims`: tabla transaccional, cada enfrentamiento entre dos clanes
en un mapa.
- `resultados`: registro oficial del resultado de un scrim. Aqui esta
la restriccion que ataca el problema del cliente: un
`UNIQUE (id_scrim)` garantiza que un scrim nunca tenga mas de un
resultado.

## Como se relacionan

`clanes` 1:N `jugadores`; `clanes` 1:N `scrims` (como local y como
visitante); `mapas` 1:N `scrims`; `scrims` 1:1 `resultados`. El
diagrama esta en [diagramas/diagrama-er.svg](diagramas/diagrama-er.svg).

## Que datos de prueba use

4 clanes, 8 jugadores, 4 mapas, 5 scrims (4 marcados `jugado` en algun
momento, 1 `programado`) y 4 resultados, incluido uno cargado por
error para un scrim que despues se descubrio que habia que cancelar.
Tambien un `INSERT` comentado que reproduce exactamente el problema
del cliente (dos resultados para el mismo scrim) 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
(el scrim que se cayo a la mitad pasa a `cancelado`) y un `DELETE`
controlado que limpia el resultado huerfano de ese scrim, sin tocar
ningun resultado de un scrim ya `jugado`.

## Que consultas responden al cliente

En [dql/consultas.sql](dql/consultas.sql): que scrims existen (JOIN
clan local-clan visitante-mapa), en que estado esta cada uno, que clan
jugo mas scrims, los scrims ordenados por fecha, y un reporte con
`GROUP BY` + `HAVING` de victorias por clan, para decidir quien
clasifica a playoffs.

## 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-073.db < ddl/schema.sql
sqlite3 ejercicio-073.db < dml/inserts.sql
sqlite3 ejercicio-073.db < dml/operaciones.sql
sqlite3 ejercicio-073.db < dql/consultas.sql
```

No suba archivos `.db`, `.sqlite`, `.sqlite3` ni `.dump`.
Loading
Loading