Capítulo 4 — Modelos de datos
Mapa de la lección
Lección 04 publicada. Mapa, ejemplo, EX010.
flowchart TD
N0["Resultados de aprendizaje y conexión"]
N1["4.1 Entidades, atributos y claves"]
N2["4.2 Restricciones: hacer imposibles los registros im"]
N3["4.3 Los valores ausentes son hallazgos"]
N4["4.4 Las combinaciones responden a las preguntas real"]
N5["4.6 Actividad práctica: consultas al inventario"]
N6["Síntesis del capítulo"]
N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> N6
N6 --> EX["Ejemplo resuelto"]
EX --> Q["Ejercicio EX010"]
Dibujo lógico
flowchart LR
N0["Resultados de aprendizaje y conexión"]
N1["4.1 Entidades, atributos y claves"]
N2["4.2 Restricciones: hacer imposibles los registros im"]
N3["4.3 Los valores ausentes son hallazgos"]
N4["4.4 Las combinaciones responden a las preguntas real"]
N5["4.6 Actividad práctica: consultas al inventario"]
N6["Síntesis del capítulo"]
N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> N6 --> Q["EX010"]
[Resultados de aprendizaje y conexión]
|
v
[4.1 Entidades, atributos y claves]
|
v
[4.2 Restricciones: hacer imposibles los registros im]
|
v
[4.3 Los valores ausentes son hallazgos]
|
v
[4.4 Las combinaciones responden a las preguntas real]
|
v
[4.6 Actividad práctica: consultas al inventario]
|
v
[Síntesis del capítulo]
|
v
[Ejemplo resuelto] --> [Ejercicio EX010]
Resultados de aprendizaje y conexión
Al terminar este capítulo, usted debería ser capaz de:
- modelar registros operativos (activos, responsables, estado del soporte) como tablas relacionadas mediante claves;
- usar restricciones para que la base de datos rechace los registros que no pueden ser ciertos;
- escribir consultas que respondan a preguntas operativas («¿qué activos críticos no tienen responsable?») en lugar de producir listas;
- explicar por qué un valor ausente (
NULL) es información y debe tratarse de forma deliberada.
Todos los capítulos posteriores dependen de registros: inventarios, registros de flujos de datos, registros de riesgos, listas de acceso, registros de evidencia. Cuando viven en hojas de cálculo que editan muchas personas, se degradan: filas duplicadas, responsables en texto libre, «pendiente» en campos de fecha. Este capítulo muestra el modelado de datos mínimo que necesita un implementador para que los registros operativos sigan siendo fiables.
4.1 Entidades, atributos y claves
Un modelo de datos indica qué tipos de cosas describen los registros (entidades: activo, responsable, contrato de soporte), qué se registra de cada una (atributos: modelo, firmware, emplazamiento, criticidad) y cómo se relacionan. Cada entidad tiene una clave primaria, un valor que identifica exactamente un registro, como una etiqueta de activo. Una clave foránea en una tabla apunta a la clave primaria de otra: el owner_id de un activo hace referencia a una fila de la tabla de responsables. Esa única decisión de diseño sustituye los nombres de responsable en texto libre, que se desvían («Water Ops», «water operations», «A. Okafor (left)»), por una referencia que o bien apunta a un responsable real o bien no.
4.2 Restricciones: hacer imposibles los registros imposibles
Una restricción es una regla que la base de datos aplica en cada escritura. NOT NULL indica que debe haber un valor; CHECK (criticality BETWEEN 1 AND 3) indica que solo se permiten esos valores; una clave foránea indica que la fila referenciada debe existir (en SQLite, la aplicación de las claves foráneas debe activarse en cada conexión, un detalle que conviene conocer porque sorprende a la gente). Las restricciones trasladan la validación de «alguien debería comprobarlo» a «el sistema se niega». Son la validación de entradas del capítulo 3 aplicada a los registros. En el Lab 4, la última consulta es exactamente esto: insertar un activo con criticidad 7 falla en la base de datos, sea quien sea quien haya escrito el script.
4.3 Los valores ausentes son hallazgos
En un inventario, un valor ausente no es un blanco; es la afirmación de que algo se desconoce. Un activo sin responsable es un activo que nadie parcheará, revisará ni dará de baja. Un activo con firmware desconocido es uno cuyas vulnerabilidades no pueden evaluarse. Por tanto, las consultas deben buscar expresamente los valores ausentes, no ignorarlos. El NULL de SQL se comporta de forma especial: owner_id = NULL nunca es verdadero; hay que escribir owner_id IS NULL. Muchos errores silenciosos en los informes nacen de olvidarlo. El activo de criticidad 1 sin responsable del Lab 4, la RTU de PS-09, solo aparece porque la consulta lo busca.
4.4 Las combinaciones responden a las preguntas reales
Las preguntas operativas suelen abarcar varias tablas. «¿Qué activos funcionan con un modelo cuyo soporte del proveedor ha terminado?» requiere combinar la tabla de activos con la de soporte por el modelo. «¿Cuántos activos de criticidad 1 tiene cada responsable?» requiere combinar activos y responsables con una combinación externa izquierda (left join), para que los activos sin responsable aparezcan como "(none)" en lugar de desaparecer del recuento. La diferencia entre una combinación interna y una externa izquierda suele ser la diferencia entre un informe que oculta un problema y uno que lo muestra.
4.5 Ejemplo resuelto: extracto del inventario de FSTS (sintético)
El Lab 4 construye un inventario pequeño en una base de datos SQLite en memoria (el módulo sqlite3 de Python, documentation) y plantea cinco preguntas. Cada respuesta permite actuar. La RTU de PS-09 no tiene responsable, es de criticidad 1 y el soporte del proveedor para su modelo terminó el 2025-12-31: tres motivos para actuar sobre el mismo activo, que una hoja de cálculo ordenada por emplazamiento nunca habría reunido. El conmutador de la estación PS-04 tiene firmware desconocido. Dos servidores ejecutan versiones de sistema operativo que figuran en la lista propia de FSTS de retirada obligatoria por política. La consulta de criticidad por responsable muestra un activo de criticidad 1 que no pertenece a nadie. Por último, la restricción CHECK rechaza un intento de insertar un activo con criticidad 7.
4.6 Actividad práctica: consultas al inventario
Lab 4 (guárdelo como lab04.py y ejecútelo con python3 lab04.py):
# Lab 4 - an asset inventory as a relational model (offline, standard library only: sqlite3)
# Synthetic: Fallowmere Shared Technology Services inventory extract.
import sqlite3
db = sqlite3.connect(":memory:")
db.executescript("""
CREATE TABLE owner(id INTEGER PRIMARY KEY, name TEXT NOT NULL, service TEXT NOT NULL);
CREATE TABLE asset(id TEXT PRIMARY KEY, kind TEXT NOT NULL, model TEXT, firmware TEXT,
site TEXT NOT NULL, owner_id INTEGER REFERENCES owner(id), criticality INTEGER CHECK (criticality BETWEEN 1 AND 3));
CREATE TABLE support(model TEXT PRIMARY KEY, vendor_end TEXT);
INSERT INTO owner VALUES (1,'Water Ops','drinking water'),(2,'Clinical Eng','hospital'),(3,'Depot IT','transit');
INSERT INTO asset VALUES
('PS04-PLC','plc','Arlen 300','4.2','PS-04',1,1),('PS07-PLC','plc','Arlen 300','3.9','PS-07',1,1),
('PS09-RTU','rtu','Kell R2','1.1','PS-09',NULL,1),('RLY-01','server','generic x86','deb12','Hub',1,1),
('HIST-01','server','generic x86','win2016','Hub',1,2),('INF-PUMP-12','infusion','Moro IP5','2.0','Ward 3',2,1),
('PACS-GW','server','generic x86','win2012r2','Hospital',2,2),('DEPOT-KIOSK','kiosk','Tella K1','8.1','Depot',3,3),
('SW-STN-04','switch','Brack 24',NULL,'PS-04',1,2);
INSERT INTO support VALUES ('Arlen 300','2027-06-30'),('Kell R2','2025-12-31'),('Moro IP5','2029-01-01'),('Tella K1','2026-03-31');
""")
q = {
"no owner": "SELECT id, site FROM asset WHERE owner_id IS NULL",
"unknown firmware": "SELECT id, site FROM asset WHERE firmware IS NULL",
"vendor support ended (as of 2026-10-12)":
"SELECT a.id, s.vendor_end FROM asset a JOIN support s ON a.model=s.model WHERE s.vendor_end < '2026-10-12'",
"OS on the service's retire-by-policy list (synthetic)": "SELECT id, firmware FROM asset WHERE firmware IN ('win2012r2','win2016')",
"criticality-1 assets by owner": """SELECT COALESCE(o.name,'(none)'), COUNT(*) FROM asset a LEFT JOIN owner o
ON a.owner_id=o.id WHERE a.criticality=1 GROUP BY o.name ORDER BY 2 DESC""",
}
for title, sql in q.items():
print(f"-- {title}")
for row in db.execute(sql):
print(" ", row)
try:
db.execute("INSERT INTO asset VALUES ('X','plc','m','1','PS-01',1,7)")
except sqlite3.IntegrityError as e:
print("constraint rejected bad row:", e)
Salida esperada (Python 3.13.5):
-- no owner
('PS09-RTU', 'PS-09')
-- unknown firmware
('SW-STN-04', 'PS-04')
-- vendor support ended (as of 2026-10-12)
('PS09-RTU', '2025-12-31')
('DEPOT-KIOSK', '2026-03-31')
-- OS on the service's retire-by-policy list (synthetic)
('HIST-01', 'win2016')
('PACS-GW', 'win2012r2')
-- criticality-1 assets by owner
('Water Ops', 3)
('Clinical Eng', 1)
('(none)', 1)
constraint rejected bad row: CHECK constraint failed: criticality BETWEEN 1 AND 3
Lea la salida como una lista de trabajo y no como un informe. La RTU de PS-09 es la primera línea de trabajo, porque en un mismo activo coinciden tres hallazgos: sin responsable, criticidad 1 y soporte del proveedor terminado el 2025-12-31. El firmware desconocido del conmutador de la estación PS-04 se convierte en una pregunta para el ingeniero de Agua, no en una suposición. Los dos servidores de la lista de retirada obligatoria de FSTS pasan al responsable de la plataforma con sus fechas de sustitución. Por último, compruebe la inserción rechazada: si la fila de criticidad 7 se hubiera aceptado, la aplicación de las claves foráneas y de CHECK no estaba activa en esa conexión, y todas las respuestas anteriores merecerían menos confianza.
4.7 Errores frecuentes y límites
- Campos de texto libre para cualquier cosa que debería hacer referencia a otro registro (responsables, emplazamientos, modelos).
- Comparar con
= NULL, de modo que los valores ausentes quedan excluidos en silencio. - Combinaciones internas en los informes de cobertura, que eliminan precisamente los registros con vínculos ausentes.
- Almacenar fechas como texto libre en formatos mezclados, de modo que las comparaciones ordenan mal; almacene ISO 8601 (
YYYY-MM-DD), que se ordena correctamente como texto. - Creer que la base de datos es la verdad. Solo es tan buena como el proceso de descubrimiento y actualización que la respalda (capítulo 11).
Un modelo de datos no puede decirle que un activo existe si nadie lo registró. Puede asegurar que lo registrado es coherente y que las lagunas son visibles.
Ejercicios — capítulo 4
EX010 — Encuentre el activo oculto. Objetivo: ver cómo una combinación interna oculta un problema. Entradas: el Lab 4. Restricciones: cambie solo la última consulta. Pasos: (1) cambie LEFT JOIN por JOIN en la consulta de criticidad por responsable; (2) ejecute y compare; (3) explique en dos frases qué activo desapareció y por qué importa en un informe de cobertura. Evidencia esperada: ambas salidas y la explicación. Rúbrica: 3 = desaparece la fila de PS09-RTU, el recuento total de criticidad 1 baja de 5 a 4, la explicación lo relaciona con activos sin responsable que desaparecen de los informes de gestión; 2 = lo observa, explicación débil; 1 = no se ejecutó; 0 = nada.
EX011 — Añada una restricción y una tabla. Objetivo: ampliar el modelo de forma segura. Entradas: el Lab 4. Restricciones: solo la biblioteca estándar. Pasos: (1) añada una tabla review (id del activo, fecha de revisión, revisor, resultado) con una clave foránea a asset y un CHECK de que el resultado es ok, issue o not-reachable; (2) active las claves foráneas con PRAGMA foreign_keys = ON; (3) muestre que insertar una revisión de un activo inexistente falla; (4) escriba una consulta que liste los activos de criticidad 1 sin revisión en los últimos 90 días. Evidencia esperada: código y salida. Rúbrica: 3 = restricción y clave foránea aplicadas, la consulta usa una combinación externa izquierda o NOT EXISTS para que aparezcan los activos nunca revisados; 2 = funciona pero omite los activos nunca revisados; 1 = solo el esquema; 0 = nada.
EX012 — De la hoja de cálculo al modelo. Objetivo: reconocer fallos de calidad de datos. Entradas: un conjunto sintético de filas de hoja de cálculo: PS-03 PLC | owner: Water Ops | fw: 4.2 | support: Jun 2027; PS-03 plc | owner: water operations | fw: ? | support: 30/06/27; PS-05 PLC | owner: A. Okafor (left) | fw: 4.1 | support: TBC. Restricciones: no se necesita código. Pasos: (1) enumere todos los fallos de calidad de datos; (2) indique qué restricción o decisión de diseño de las pautas del capítulo 4 sobre claves, restricciones y valores ausentes evita cada uno. Evidencia esperada: tabla. Rúbrica: 3 = activo duplicado con mayúsculas incoherentes, variantes de responsable en texto libre, una persona en lugar de una función como responsable y además una baja, firmware desconocido como "?", formatos de fecha mezclados, "TBC"; cada uno asignado a clave primaria, clave foránea, NOT NULL/desconocido explícito o fechas ISO; 2 = la mayoría encontrados; 1 = pocos; 0 = nada.
Síntesis del capítulo
Los registros operativos solo son útiles si se mantienen coherentes. Modélelos como entidades con claves, vincúlelos con claves foráneas en lugar de texto libre y deje que las restricciones rechacen los registros imposibles. Trate los valores ausentes como hallazgos, búsquelos expresamente y use combinaciones externas izquierdas en cualquier informe de cobertura. Un buen modelo hace visibles las lagunas; no puede encontrar activos que nadie registró.
Siga estudiando — capítulo 04 de 30
← Capítulo 03 · Índice de capítulos del curso · Capítulo 05 →
Continúe con la siguiente lección escrita.