Usar la IA con Criterio en Bases de Datos
Ya sabés escribir DDL, DML, JOINs, transacciones, procedimientos y roles con tus propias manos. Esta guía no enseña SQL nuevo: te pone en el lugar de alguien que usa una IA para ir más rápido con una base de datos real. En cada decisión vas a poder ejecutar vos mismo, contra PostgreSQL real, el SQL que sugirió la IA —y ver con tus propios ojos si funciona, si falla, o si da un resultado que parece bien pero está mal— antes de que se desbloquee el camino correcto.
👁 — vistas · Ing. Roy Carrasco, Facultad de Ingeniería de Sistemas · UAB
Vas a acompañar a Iván, practicante en el área de sistemas de una empresa que gestiona pedidos con el mismo esquema cliente/producto/pedido/detalle_pedido que ya conocés, mientras arma un reporte apoyándose en una IA. Cada prompt que Iván escribe y cada respuesta que recibe están al pie de la letra —y cada consulta corre de verdad, con PGlite, el mismo PostgreSQL real compilado a WebAssembly que ya usaste en los talleres.
El primer pedido a la IA
A Iván le piden un reporte: "los pedidos de cada cliente, con el total gastado". Para ir rápido, abre el chat de una IA generativa y empieza a escribir el pedido.
Todavía no le mostró a la IA ni una sola tabla de su base de datos. Probemos qué pasa si sigue así —en un momento vas a poder ejecutar vos mismo, contra PostgreSQL real, lo que la IA responda.
¿Qué debería hacer Iván antes de pedirle la consulta a la IA?
Exacto. Sin el esquema real, la IA rellena el vacío con lo más probable estadísticamente —no con lo que existe en esta base de datos. Pegar el DDL real elimina esa adivinanza desde el principio. Seguí leyendo ↓
La IA no tiene ningún acceso a la base de datos de Iván. Sin el esquema real, "los nombres típicos" son una apuesta —puede acertar por casualidad, pero no porque sepa algo de este sistema en particular. Probá con otra opción.
Un tutorial ajeno tiene su propio esquema, con sus propios nombres de tabla y columna —adaptarlo "a ojo" reintroduce exactamente el mismo problema: la IA sigue sin conocer el esquema real de Iván. Probá con otra opción.
Dale el esquema real, no una descripción de memoria
Esta es la base de datos real de Iván —la misma que ya construiste en la guía de DDL— ya cargada con clientes, productos y algunos pedidos de ejemplo:
CREATE TABLE cliente (
cliente_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
cliente_nombre VARCHAR(120) NOT NULL
);
CREATE TABLE producto (
producto_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
producto_nombre VARCHAR(120) NOT NULL,
precio NUMERIC(10,2) NOT NULL,
activo BOOLEAN NOT NULL DEFAULT true
);
CREATE TABLE pedido (
pedido_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
fecha DATE NOT NULL DEFAULT CURRENT_DATE,
cliente_id INTEGER NOT NULL REFERENCES cliente(cliente_id)
);
CREATE TABLE detalle_pedido (
pedido_id INTEGER REFERENCES pedido(pedido_id) ON DELETE CASCADE,
producto_id INTEGER REFERENCES producto(producto_id) ON DELETE RESTRICT,
cantidad INTEGER NOT NULL CHECK (cantidad > 0),
PRIMARY KEY (pedido_id, producto_id)
);
De acá en adelante, cada botón ▶ Ejecutar corre PostgreSQL real —el mismo motor WebAssembly de los talleres, PGlite— contra esta misma base de datos.
"Tengo una base de datos de pedidos. Dame una consulta que traiga el nombre y el correo de mis clientes."
SELECT nombre, email FROM clientes;
Ejecutala vos, contra la base de datos real de Iván:
Compará con el prompt correcto: pegar el esquema real antes de pedir la consulta.
"Este es el esquema real de mi base de datos: [los 4 CREATE TABLE de arriba]. Dame una consulta que traiga el nombre de mis clientes."
SELECT cliente_nombre FROM cliente;
De acá en más, este editor queda disponible para lo que quieras probar en cualquier momento de la guía:
Sobre cómo Iván le compartió el esquema real a la IA:
1. La vista del catálogo de PostgreSQL que lista tablas y columnas reales es .
2. En vez de escribir el esquema de memoria, lo más confiable es .
La IA responde — pero con un problema
Con el esquema ya compartido, Iván pide: "dame los nombres de los clientes". La IA responde:
SELECT nombre_cliente FROM cliente;
Ejecutala vos:
¿Qué hace Iván ante ese error?
PostgreSQL no tiene ningún error interno acá: te dice exactamente qué columna no encontró. Repetir la misma consulta no cambia nada —el nombre inventado sigue sin existir. Probá con otra opción.
Correcto. El mensaje de PostgreSQL señala exactamente qué nombre no existe —y como Iván ya tiene el esquema real a mano, corregirlo es inmediato. Este tipo de alucinación truena fuerte y claro. Pero no todas avisan así de directo. Seguí leyendo ↓
Abandonar la IA por completo es una reacción exagerada: el error de PostgreSQL identifica el problema con precisión (una sola columna con el nombre invertido). Corregirlo y seguir usando la IA para el resto es más rápido que reescribir todo desde cero. Probá con otra opción.
Detectar alucinaciones: cuando el error sí avisa
"Alucinación" no es una metáfora vacía: la IA inventa una tabla o columna que suena perfectamente plausible pero no existe en tu esquema, porque genera la secuencia de palabras más probable, no una consulta verificada. La buena noticia es que PostgreSQL casi siempre la rechaza con un error claro, apenas la ejecutás. El error que acabás de ver es un caso típico. Dos más, para reconocer el patrón:
"Ventas" es un nombre de tabla genérico que aparece en miles de ejemplos con los que se entrenó la IA —pero en el esquema de Iván esa tabla nunca existió.
El JOIN está perfecto —esa parte la IA la razonó bien— pero pedido no tiene ninguna columna total: eso se calcula sumando detalle_pedido, no viene guardado directamente.
Como acabás de comprobar tres veces, PostgreSQL rechaza el nombre inventado con un error claro apenas intentás ejecutar la consulta —no se ejecuta a medias ni da un resultado engañoso. El riesgo real no es que el error pase desapercibido, sino perder minutos revisando "qué está mal" antes de notar que el nombre nunca existió en tu esquema.
Une cada consulta con el error real de PostgreSQL que acabás de ver, sobre el esquema de Iván.
Ahora la consulta "corre" sin problema
Ya con los nombres corregidos, Iván pide: "dame el total gastado por Ana Rojas". La IA responde:
SELECT c.cliente_nombre, SUM(pr.precio) AS total
FROM cliente c
JOIN pedido p ON p.cliente_id = c.cliente_id
JOIN detalle_pedido dp ON dp.pedido_id = p.pedido_id
JOIN producto pr ON pr.producto_id = dp.producto_id
WHERE c.cliente_nombre = 'Ana Rojas'
GROUP BY c.cliente_nombre;
Ejecutala vos, y fijate el resultado:
Corre sin ningún error. La IA agrega: "Listo, la consulta ya está optimizada."
¿Qué hace Iván antes de poner ese número en el reporte?
"Sin error" solo confirma que la sintaxis es válida y que los nombres existen —no que la lógica sea correcta. Probá con otra opción.
La IA no ejecutó nada contra la base de datos real la primera vez, y preguntarle de nuevo tampoco lo hace —solo genera otra respuesta de texto, igual de no verificada que la primera. Probá con otra opción.
Correcto. Vamos a calcularlo juntos y comparar contra lo que acabás de ejecutar. Seguí leyendo ↓
Qué falló: faltó multiplicar por la cantidad
Estos son los pedidos reales de Ana Rojas en la base de datos de ejemplo:
| Producto | Cantidad | Precio | Real (cant × precio) |
|---|---|---|---|
| Mouse óptico | 2 | 80.00 | 160.00 |
| Monitor 24" | 1 | 620.00 | 620.00 |
| Teclado mecánico | 1 | 250.00 | 250.00 |
| Total real | 1030.00 | ||
La consulta de la IA sumó pr.precio una sola vez por cada línea, sin multiplicarlo por detalle_pedido.cantidad —por eso te dio 950.00 en vez de 1030.00. Confirmalo corriendo la versión que sí multiplica:
1030.00 contra 950.00: una diferencia de exactamente Bs 80 —el segundo mouse que la consulta de la IA nunca contó. Ningún mensaje de error avisó de esto: el único modo de detectarlo fue conocer los datos reales y comparar.
Sin ningún WHERE ni ON que relacione ambas tablas, esta consulta también corre sin error. Con 3 clientes y 4 pedidos en la base, ¿cuántas filas esperás? Ejecutala y contalas:
No son 4 filas (una por pedido): son 12, el producto cartesiano de las dos tablas —cada cliente aparece emparejado con todos los pedidos, no solo los suyos.
La defensa contra los dos casos es la misma: correr EXPLAIN / EXPLAIN ANALYZE vos mismo y mirar qué tablas toca el plan, tal como aprendiste en la guía de Funciones de Ventana e Índices —no la palabra de la IA sobre si "ya está optimizada".
Si una consulta SQL sugerida por una IA se ejecuta sin lanzar ningún error, eso garantiza que el resultado es correcto.
El reporte necesita un buscador
El reporte ya casi está listo. Falta un buscador para que el usuario escriba el nombre de un cliente y filtre la tabla. Iván le pide a la IA el código Python, y recibe esto:
nombre = input("Buscar cliente: ")
consulta = "SELECT * FROM cliente WHERE cliente_nombre = '" + nombre + "'"
cursor.execute(consulta)
¿Qué hace Iván con este código antes de subirlo al reporte?
Correcto. Con un parámetro separado, el driver nunca interpreta el valor del usuario como parte de la instrucción SQL, sin importar qué escriba. Seguí leyendo para comprobar el porqué exacto, corriéndolo vos mismo. ↓
Que funcione con nombres normales no prueba nada: el riesgo aparece con nombres construidos a propósito, como uno que contenga una comilla. Probá con otra opción.
Filtrar "caracteres raros" a mano es frágil —siempre queda algún caso sin cubrir, y el problema de fondo (concatenar texto del usuario dentro del SQL) sigue intacto. Probá con otra opción.
Por qué ese código es un riesgo real
Si alguien escribe ' OR '1'='1 como "nombre" en el buscador de Iván, la consulta que termina ejecutándose contra la base de datos real es esta. Ejecutala vos y mirá cuántas filas devuelve:
Devuelve los 3 clientes de la base de datos —no el cliente puntual que alguien buscaba, sino una condición siempre verdadera que ignora por completo el filtro. Con texto más agresivo, la misma técnica puede llegar a borrar filas si el driver lo permite. La corrección es un parámetro separado, no texto concatenado:
nombre = input("Buscar cliente: ")
consulta = "SELECT * FROM cliente WHERE cliente_nombre = %s"
cursor.execute(consulta, (nombre,))
Con %s más una tupla aparte, el driver envía el valor de nombre separado de la instrucción SQL. Por más que el usuario escriba comillas o palabras clave de SQL, se trata siempre como un simple valor de texto —el equivalente sería buscar un cliente llamado literalmente ' OR '1'='1, que no existe, así que devolvería 0 filas en vez de las 3 que acabás de ver.
Clasificá cada línea de código según su riesgo de inyección SQL.
Una decisión que no es solo código
Con el reporte terminado, el líder técnico le pide a Iván una cosa más: va a correr con muchos usuarios consultando al mismo tiempo, y hay que decidir el nivel de aislamiento de la transacción que arma los datos. "Preguntale a la IA y aplicá lo que te diga", le sugieren.
PGlite —como viste en la guía de Transacciones— corre en una sola conexión dentro de esta pestaña: no puede simular dos usuarios reales chocando al mismo tiempo sobre las mismas filas. Esta decisión se razona con el mismo criterio, aunque no se pueda demostrar en vivo acá.
¿Qué hace Iván?
La IA no conoce cuántos usuarios reales van a chocar sobre las mismas filas, ni qué tan grave sería un dato inconsistente en este sistema puntual —ese contexto es justo lo que le falta para decidir bien. Probá con otra opción.
Ignorar la decisión no la evita: el nivel de aislamiento por defecto también es una elección, solo que tomada sin pensarla. Probá con otra opción.
Correcto. La IA es un buen apoyo para explicar las opciones —la decisión final, que depende de conocer el sistema real, le corresponde a Iván. Seguí leyendo ↓
Cuándo la decisión final no se delega
Para tareas mecánicas y repetitivas, apoyarse en una IA y revisar el resultado alcanza. Pero hay decisiones donde el contexto de negocio —el que la IA no tiene— es justo lo que define la respuesta correcta.
| Situación | Por qué |
|---|---|
| Diseño de esquema desde cero | Normalizar y elegir claves depende de reglas de negocio que solo vos conocés —no de un patrón genérico de "cómo se modela un sistema de pedidos". |
| Transacciones con concurrencia real | El nivel de aislamiento exige entender qué puede pasar si dos usuarios operan al mismo tiempo sobre las mismas filas —un análisis del sistema completo, no de sintaxis. |
| Borrados o migraciones irreversibles en producción | La IA no sabe qué backup existe ni qué tan crítica es esa tabla para el negocio. |
Para cada tarea, decidí si Iván puede apoyarse en una IA (revisando el resultado) o si la decisión final tiene que ser suya.
El recorrido completo de Iván
Las cinco decisiones que acabás de recorrer —y de verificar con tus propias manos— con Iván, resumidas con la señal de alerta que avisa que algo salió mal en cada una.
| Momento | Qué hacer | Señal de alerta |
|---|---|---|
| Antes de pedir la consulta | Pegar el esquema real (CREATE TABLE o information_schema). | La IA "adivina" nombres de tabla o columna. |
| La IA responde con una tabla/columna | Verificar que exista en tu esquema real, ejecutándola. | relation ... does not exist / column ... does not exist. |
| La consulta corre sin error | Correr EXPLAIN y comparar el resultado contra un cálculo propio. | Un número "razonable" que nadie verificó. |
| La IA sugiere código de aplicación | Usar siempre consultas parametrizadas, nunca texto concatenado. | El valor del usuario se pega con + dentro del SQL. |
| La IA opina sobre una decisión crítica | Pedirle explicación, pero decidir vos con el contexto real del sistema. | Copiar la sugerencia sin evaluar el escenario propio. |
Actividades de repaso
Un repaso integrador de todo el recorrido. Cada actividad se corrige al instante; tu progreso se guarda automáticamente en este navegador.
Antes de los quizzes, una verificación más con tus propias manos. Sofía Paz también tiene un pedido con dos líneas —escribí vos la consulta que calcula su total real (cantidad × precio) y ejecutala:
Ver el total esperado
Bs 580.00 (1 mouse óptico + 2 teclados mecánicos).
Une cada señal con lo que indica.
Un SUM() generado por una IA corre sin error y da un total menor al real, porque olvidó multiplicar por la cantidad de cada línea. ¿Cómo se llama este tipo de problema?
Que un código "funcione bien" con datos de prueba normales demuestra que no es vulnerable a inyección SQL.
🎯 Reto Final: tu propio prompt
Ahora te toca con una base de datos real tuya (de una guía anterior, un proyecto propio, o cualquiera que conozcas). En el cuadro de abajo escribí el prompt completo que le darías a una IA para pedirle una consulta, siguiendo lo que aprendiste con Iván.
Tu prompt: qué esquema le compartirías, qué le pedirías que verifique, y qué no le delegarías.
- El prompt incluye el CREATE TABLE real (o el resultado de information_schema), no una descripción de memoria.
- Pide explícitamente que la IA use solo los nombres de tabla/columna que aparecen en ese esquema.
- Si el pedido incluye código de aplicación, exige consultas parametrizadas, no texto concatenado.
- Deja claro que el resultado se va a verificar ejecutándolo (o con EXPLAIN, o con un cálculo propio) antes de darlo por bueno.
- No le delega a la IA ninguna decisión de diseño de esquema ni de nivel de aislamiento de una transacción crítica.
Cheat Sheet
| Práctica | Por qué importa |
|---|---|
Pegar el CREATE TABLE real antes de pedir una consulta | Sin esquema real, la IA adivina nombres de tabla y columna. |
information_schema.columns / \d tabla | Formas rápidas de sacar el esquema exacto, sin transcribir de memoria. |
ERROR: relation "x" does not exist | Tabla inventada por la IA — no existe en tu esquema. |
ERROR: column "x" does not exist | Columna inventada o con el nombre real equivocado. |
| Un resultado sin error puede seguir siendo incorrecto | Errores de lógica (JOIN sin condición, SUM sin multiplicar) no lanzan ningún mensaje. |
EXPLAIN / EXPLAIN ANALYZE | La única forma de verificar el plan real, no la palabra de la IA. |
Consultas parametrizadas (%s + tupla), nunca + con datos del usuario | Evita inyección SQL en código generado. |
| Diseño de esquema y transacciones críticas | Decisiones que la IA puede opinar, pero que te corresponde tomar a vos. |
¿Sabías que...?
🧠 "Alucinación" no es una metáfora vacía
El término es el mismo que usan los papers de investigación en IA: describe cuando un modelo genera texto que suena coherente y seguro pero no corresponde a ningún hecho real —incluida la existencia de una tabla o columna que nunca existió.
🐘 PostgreSQL no "corrige" tu SQL por vos
A diferencia de un corrector ortográfico, PostgreSQL nunca adivina qué columna quisiste decir cuando escribís un nombre inexistente. Solo rechaza la consulta con el nombre exacto que no encontró.
💉 La inyección SQL es más vieja que la mayoría de los frameworks web
Se documenta formalmente desde fines de los años 90. Que siga apareciendo en código sugerido por herramientas modernas no es una novedad técnica: es la misma vulnerabilidad de siempre, reproducida por un patrón de código viejo que todavía circula en el material de entrenamiento.