Todos los recursos

El mismo prompt que escribe un SELECT limpio se puede dejar convencer de hacer un DELETE, o de correr una consulta sin LIMIT que escanea tu tabla más grande a las 9 de la mañana. Este es un prompt completo y listo para copiar y pegar que acota qué puede significar 'SQL', además de cómo darle el esquema, las dos variables donde está el verdadero valor, las variantes que vale la pena guardar y la única regla que el prompt no puede hacer cumplir por sí solo: esa última va en un rol de base de datos de solo lectura, no en cómo redactes el prompt.

Un prompt de lenguaje natural a SQL, hecho para quedarse en solo lectura

En resumen

  • El prompt obliga a un solo SELECT, prohíbe cualquier palabra de escritura, DDL o transacción y exige un LIMIT a menos que la pregunta sea un agregado.
  • Devuelve el SQL, una línea con las 'tablas usadas' y una salida explícita de 'no se puede responder con este esquema', así nunca se inventa una columna.
  • Dale el esquema como una lista estática de tablas y columnas, nunca una conexión en vivo. El modelo debe razonar sobre texto, no tocar la base de datos.
  • El prompt no es la barrera de seguridad. Lo que de verdad frena una escritura es un rol de base de datos de solo lectura con permisos únicamente de SELECT. Usa los dos.
  • Dos variables concentran casi todo el valor: el bloque de esquema y la línea de dialecto y convenciones. Llénalas con cuidado. Lo demás es el riel de seguridad.

Pregúntale a un modelo "¿cuántos usuarios activos se registraron el mes pasado?" y que te devuelva SQL que funciona es uno de los usos realmente buenos de un LLM sobre una base de datos. También es uno de los más fáciles de echar a perder al ponerlo en producción. El mismo prompt que produce un SELECT limpio, si lo dejas, va a emitir un DELETE porque alguien planteó una pregunta como si fuera una orden, o te va a entregar una consulta sin LIMIT que escanea tu tabla más grande en plena hora pico. Este es un prompt completo que cierra las dos brechas acotando qué puede significar "SQL": solo lectura, con límites, honesto con el esquema. Las secciones que siguen explican cómo adaptarlo, qué hacen las variables y el único riel de seguridad que nunca debes meter en el prompt, porque ahí no funciona.

El plato fuerte está más abajo. Úsalo tal cual primero contra una read replica, observa cómo se comporta con preguntas reales y solo entonces ajústalo. Las cláusulas son el producto. El modelo siempre supo escribir SQL; lo que le faltaba era un contrato sobre qué tipo de SQL.

01 · El prompt

Pégalo como system prompt de un subagente de consultas, o como primer mensaje antes de la pregunta del usuario en el chat. Reemplaza las dos variables entre corchetes antes de usarlo por primera vez. Están documentadas en la sección 03.

You write read-only PostgreSQL. You translate one natural-language question into
ONE SQL query and nothing else.

## Schema
This is the entire schema you may use. It lists tables and columns only. If a
table or column is not listed here, it does not exist for you.

[SCHEMA_BLOCK]

## Output contract
Translate the user's question into exactly ONE SELECT statement.
- Read-only. NEVER emit INSERT, UPDATE, DELETE, MERGE, TRUNCATE, ALTER, DROP,
  CREATE, GRANT, COPY, or any statement that writes, locks, or changes state.
- NEVER use multiple statements, semicolons that start a second statement, CTEs
  that write, or transaction control (BEGIN/COMMIT). One SELECT, one result.
- Always include a LIMIT (default 100) UNLESS the query is a pure aggregate
  (only GROUP BY rollups / scalar aggregates with no row-level output).
- Qualify every column with its table or alias. Alias every table you join.
- Prefer explicit JOIN ... ON over comma joins. Filter with parameters where the
  user gave a literal value; do not inline user-supplied strings.
- Use [DIALECT_AND_CONVENTIONS] for date handling, casing, and house rules.

## When you cannot answer
If the schema cannot answer the question — a needed column or table is missing,
or the question is ambiguous about which table it means — do NOT guess and do NOT
invent a column. Reply with exactly:
  CANNOT ANSWER: <one sentence on what is missing or ambiguous>
and stop. An honest refusal is correct; a plausible wrong query is not.

## Response format
On success, output exactly two things and nothing else:
1. A fenced sql block containing the single SELECT.
2. One final line: "Tables used: <comma-separated list>".
No prose, no explanation of what the query does, no alternatives.

Atención

Este prompt es un riel de usabilidad, no una barrera de seguridad. A un modelo al que le dijiste "solo lectura" igual lo pueden convencer de escribir, y una sola pregunta bien armada puede colar una escritura entre las palabras. Corre cada consulta generada bajo un rol de base de datos que tenga SELECT sobre las tablas permitidas y nada más. El prompt hace probable la salida buena; el rol hace imposible la salida mala.

02 · Cómo darle el esquema

El bloque de esquema es la entrada más importante, y la más fácil de arruinar compartiendo de más. El modelo necesita la forma de tus datos, no una puerta abierta hacia ellos.

Lista tablas y columnas, no una conexión

Pega una descripción estática, en texto, de las tablas relevantes: nombre, columnas, tipos y las foreign keys que importan para los joins. No le des al modelo credenciales de base de datos, un connection string ni una herramienta que corra SQL arbitrario durante la generación. La tarea es "razona sobre este texto y produce una consulta", que después una persona corre bajo un rol con permisos limitados. Mantener separadas la generación y la ejecución es lo que permite que el rol sea la barrera de verdad.

Limita el esquema al dominio de la pregunta

Un esquema de 200 tablas dentro del prompt es caro en cada llamada y, encima, contraproducente: el modelo termina eligiendo tablas que parecen correctas pero no lo son cuando seis de ellas tienen una columna status. Dale solo las tablas que podrían responder el tipo de preguntas que maneja esta funcionalidad. Para un panel de analítica de cara al cliente, eso podrían ser una docena de tablas, no el warehouse completo. Esquema más pequeño, menos joins inventados, menos costo en tokens, y eso se acumula.

Un bloque de esquema que funciona bien se ve así:

[SCHEMA_BLOCK]:
users(id uuid pk, email text, created_at timestamptz, plan text, deleted_at timestamptz)
subscriptions(id uuid pk, user_id uuid fk->users.id, status text, started_at timestamptz, canceled_at timestamptz)
events(id bigint pk, user_id uuid fk->users.id, name text, occurred_at timestamptz)

Notes:
- A user is "active" when deleted_at IS NULL and a subscription row has status='active'.
- All timestamps are UTC (timestamptz). Treat "last month" as the prior calendar month.

Consejo

Pon tus definiciones de negocio en las notas del esquema, no en tu cabeza. "Activo significa deleted_at IS NULL y status='active'" es justo el tipo de regla que el modelo no puede deducir y va a equivocar en silencio. Una línea de definición elimina toda una categoría de respuestas que parecen bien pero están mal.

03 · Las variables, y qué poner en ellas

Hay dos variables entre corchetes. Ahí está la adaptación. El resto del prompt es el riel de seguridad y casi siempre conviene dejarlo como está.

  1. [SCHEMA_BLOCK] es el listado acotado de tablas y columnas más las notas de reglas de negocio de la sección 02. Aquí vive el noventa por ciento de la exactitud. Una foreign key que falta acá termina en un join equivocado. Una definición que falta termina en un filtro equivocado.
  2. [DIALECT_AND_CONVENTIONS] son las reglas propias de tu base de datos que el modelo necesita para acertar los detalles. Mantenlo en pocas líneas: el dialecto de SQL (Postgres aquí, pero déjalo escrito), cómo se manejan fechas y zonas horarias, el casing de los identificadores y cualquier convención de la casa. Para un proyecto de Ilustrari podría decir: PostgreSQL 16. Todos los timestamps son timestamptz en UTC; los períodos de reporte se muestran en America/New_York. Identificadores en snake_case. Usa date_trunc para los rollups por período. El dinero se guarda en centavos como enteros.

El prompt ya trae fijos los rieles de seguridad: la lista de palabras prohibidas, la regla de un solo statement, el LIMIT por defecto. Así que casi nunca los tocas. Si te encuentras aflojándolos, esa es la señal para echar mano de una variante de la sección siguiente, no para debilitar la base.

04 · Variantes que vale la pena guardar

El prompt base es la opción correcta por defecto para preguntas ad-hoc sobre una read replica. Unas pocas variantes se ganan su lugar; resiste la tentación de armar más de las que vas a usar.

Parametrizado para un endpoint de app

Cuando el SQL alimenta un endpoint real y no la terminal de una persona, no dejes que el modelo meta los valores del usuario inline. Agrega: Return the query with placeholders ($1, $2, …) for every user-supplied value, and a separate JSON array of the parameter values in order. Así tu código corre un prepared statement. Esto neutraliza el vector de inyección que el prompt base apenas desalienta, y permite que la base de datos guarde en caché el plan según la forma de la consulta.

Solo agregados / tiles de dashboard

Para un dashboard que solo muestra totales y rollups, aprieta el contrato: Output must be a pure aggregate: GROUP BY rollups or scalar aggregates only, never row-level detail. Reject any question that would return individual rows with CANNOT ANSWER. Esto evita que un tile del dashboard suelte sin querer diez mil filas de usuarios porque alguien preguntó "qué usuarios se dieron de baja" en vez de "cuántos".

Explica primero, consulta después (para revisión)

Cuando una persona va a revisar antes de correr la consulta, invierte el formato: First output one sentence stating, in plain language, what the query will count or return and which filters it applies. Then output the SQL. Ese resumen en lenguaje claro es donde captas el "ah, está contando todos los registros, no los activos" antes de que la consulta corra. Más barato que leer SQL, y deja al descubierto de inmediato una definición mal entendida.

05 · Detalles que deciden si confías en él

  • El prompt no es la barrera, lo repito. Cada línea sobre "solo lectura" es una función de usabilidad que mantiene limpio el caso común. Lo que detiene una escritura es un rol de Postgres con GRANT SELECT sobre las tablas permitidas y ningún permiso de escritura, DDL ni ejecución de funciones. Ponle además un statement_timeout y un tope de filas o de costo a ese rol, para que un LIMIT olvidado no pueda retener una conexión diez minutos.
  • Un esquema desactualizado miente con total seguridad. Si renombras una columna y el bloque de esquema sigue mostrando el nombre viejo, el modelo escribe SQL que se ve correcto contra una columna que ya no existe, y te llevas un error en runtime, no un rechazo. Genera el bloque de esquema desde el catálogo en vivo (una consulta contra information_schema) en vez de mantenerlo a mano, y regenéralo en cada migración.
  • La ambigüedad en los agregados es la respuesta equivocada que pasa callada. "¿Cuántos usuarios?", ¿usuarios distintos o filas? "El mes pasado", ¿mes calendario o los últimos 30 días? El modelo va a elegir una opción y se va a ver muy seguro. La solución está en tus manos: pon las definiciones en las notas del esquema (sección 02) para que la respuesta quede determinada y no adivinada.
  • Va a hacer el join por la columna equivocada si la FK no está declarada. Dos tablas con un user_id y un id se pueden unir de cuatro maneras; solo una es la correcta. Lista las foreign keys de forma explícita en el bloque de esquema. Una relación sin declarar es una invitación a un join que parece bien pero está mal.

Importante

Prueba siempre las preguntas nuevas contra una read replica primero, nunca contra el primary. Una read replica te da la barrera del rol, el statement timeout y la libertad de dejar que una consulta mala falle a gritos sin tocar la carga de producción ni arriesgar un camino de escritura que se te olvidó cerrar.

Un prompt de lenguaje natural a SQL no es magia y no debería sentirse como tal. Es un contrato estrecho (un SELECT, con límites, honesto con su esquema) emparejado con un rol de base de datos que vuelve ese contrato físicamente cierto. El prompt te consigue consultas limpias y al punto la mayoría de las veces. El rol es lo que te deja dormir tranquilo cuando el prompt se equivoca, cosa que tarde o temprano va a pasar. Pégalo, limita el esquema, llena la línea de dialecto, apúntalo a una replica bajo un rol de solo SELECT y solo entonces empieza a confiarle preguntas reales.

Puntos clave

  • La tarea del prompt es acotar qué significa 'SQL': un solo SELECT de solo lectura, limitado por un LIMIT, honesto con su esquema y con un camino de rechazo explícito.
  • El prompt es un riel de usabilidad, nunca la barrera de seguridad. Un rol de Postgres de solo SELECT con statement timeout es lo que de verdad detiene una escritura.
  • El bloque de esquema concentra casi toda la exactitud: limítalo al dominio, lista las foreign keys y mete las definiciones de negocio en sus notas.
  • Genera el esquema desde information_schema y regenéralo en cada migración, para que un bloque desactualizado no haga que el modelo escriba SQL equivocado pero muy seguro de sí.
  • Para endpoints de app usa la variante parametrizada; para dashboards usa la variante de solo agregados; y prueba siempre primero contra una read replica.

Preguntas frecuentes

¿Por qué no confiar solo en las reglas de 'solo lectura' del prompt para que todo quede seguro?

Porque un prompt es una sugerencia que el modelo suele seguir, no un muro que no pueda cruzar. Una pregunta bien armada, un string de prompt-injection metido en los datos o un modelo en un mal momento de sampling pueden producir una escritura a pesar de lo que digan las instrucciones. Las palabras mantienen limpio el caso común; un rol de Postgres con permisos solo de SELECT es lo que vuelve una escritura físicamente imposible. Usa los dos, y trata al rol como la barrera real.

¿Debo darle al modelo una conexión en vivo a la base de datos para que explore el esquema por su cuenta?

No. La generación y la ejecución deben mantenerse separadas. Pega un listado de esquema estático como texto y deja que el modelo razone sobre él, y después una persona o un script sencillo corre la consulta resultante bajo un rol con permisos limitados. Una conexión en vivo durante la generación rompe esa separación y convierte al modelo en algo que toca la base de datos directamente, que es justo la barrera que quieres dejar afuera, en código simple y auditable.

¿Qué hace que el modelo invente una columna que no existe?

Casi siempre la pregunta da por sentado datos que el esquema no tiene, y el modelo prefiere producir una consulta que parezca creíble antes que admitir el hueco. La salida 'CANNOT ANSWER' es la solución: convierte el rechazo en una salida explícita y esperada, así el modelo deja de tratar una columna faltante como un problema que hay que disimular. Si aun así ves columnas inventadas, lo más probable es que tu bloque de esquema sea demasiado grande o esté poco definido. Recórtalo más y lista las foreign keys.

¿Cómo evito que el bloque de esquema se desincronice de la base de datos real?

Genéralo desde el catálogo en vivo en vez de mantenerlo a mano. Una consulta contra information_schema (tablas, columnas, tipos, foreign keys) produce el bloque de forma determinista, y lo regeneras en cada migración para que un rename o una columna nueva aparezcan de inmediato. Un esquema editado a mano miente con total seguridad: el modelo escribe SQL que se ve correcto contra una columna que borraste, y te llevas un error en runtime en vez de un rechazo limpio.

¿Puedo conectar esto a un endpoint de cara al usuario, o es solo para uso interno?

Sí puedes, pero usa la variante parametrizada y aprieta el resto. Haz que el modelo devuelva placeholders más un array de parámetros aparte para que tu código corra un prepared statement, lo que cierra el vector de inyección que el prompt base apenas desalienta. Apúntalo a una read replica, córrelo bajo un rol de solo SELECT con statement timeout y tope de filas, y considera la variante de solo agregados para que una pregunta no pueda soltar filas en crudo. El modelo produce la forma de la consulta. Tu infraestructura es la dueña de los límites.

¿Por qué se equivoca por poco en las preguntas de agregados, como contar filas en vez de usuarios distintos?

Porque la pregunta es ambigua y el modelo elige una interpretación y se aferra a ella con seguridad. '¿Cuántos usuarios?' puede significar usuarios distintos o filas. 'El mes pasado' puede ser el mes calendario o los últimos 30 días. El prompt no puede resolver lo que nunca definiste, así que pon las definiciones en las notas del esquema. 'Activo significa deleted_at IS NULL y status active; el mes pasado es el mes calendario anterior' convierte una adivinanza en una respuesta determinada.

¿Prefieres que lo hagamos por ti?

Esto mismo lo construimos para negocios como el tuyo. La primera conversación es gratis y sin compromiso.

Escríbenos por WhatsApp

Escríbenos por WhatsApp

Escanéalo con tu teléfono para escribirnos por WhatsApp.

Escanéalo con tu teléfono para escribirnos por WhatsApp.

¿Estás desde el teléfono y no puedes escanear? Escríbenos a info@ilustrari.com

Primera conversación gratis. Te responde el fundador.

Recursos relacionados

PromptClaude Code

Un system prompt de revisor de código estricto que sí puedes reutilizar

El modo de fallo típico de un revisor LLM es la cortesía: encuentra tres bugs reales y los sepulta bajo veinte notas de 'considera extraer una constante'. Este es un system prompt completo y listo para copiar que impone un contrato de severidad para que la señal quede siempre arriba, además de cómo adaptarlo, para qué sirve cada placeholder, las variantes que vale la pena conservar y las cláusulas que, sin que te des cuenta, deciden si confías en lo que te devuelve.

20 abr 202612 min de lectura
PromptSupabase

Redactor de políticas RLS: un prompt que escribe políticas de Supabase y luego las ataca

Row Level Security es donde las apps de Supabase se filtran sin que nadie se dé cuenta: una política que se lee bien se olvida del INSERT, confía en una columna que pone el cliente, o cuida el USING pero no el WITH CHECK. Este es el prompt, listo para copiar y pegar, que usamos para redactar el set completo de políticas de una tabla, una política por operación, y después hacer que el modelo le haga red-team a su propio trabajo en una sección de NOTAS DE AMENAZA. Te llevas el template, las variables que tienes que rellenar, variantes para multi-tenant y storage, y los detalles que convierten una política de aspecto impecable en una lectura cruzada entre tenants.

12 abr 202612 min de lectura
PromptDocker

Un prompt que endurece tu Dockerfile y te explica cada cambio

La mayoría de los Dockerfiles corren como root, están inflados y no fijan versiones, y como los arreglos son tan mecánicos, la gente pega una reescritura de 'best practices' sin entender qué resolvió. Este es un prompt completo y listo para copiar que reescribe tu Dockerfile a multi-etapa, sin root, con versiones fijadas y con las capas ordenadas para cache, y conecta cada cambio con el riesgo concreto que elimina, además de cómo adaptarlo según el lenguaje, cuáles son los placeholders que cargan el valor, qué variantes vale la pena guardar y el detalle que convierte una salida limpia del chat en un CI en rojo.

11 abr 202611 min de lectura