Prompts para explicar código: del regex al stack trace

Dieciséis preguntas listas para el explicador de código con IA. Cada una trae ya el fragmento, el patrón, el comando o el stack trace, y fija para quién es la explicación y hasta dónde tiene que llegar.

Entender código ajeno

Una función sin un solo comentario y una decisión rara cuyo motivo se fue de la empresa con su autor: entender código ajeno empieza por un repaso línea por línea, en un lenguaje que tú no escribes.

Función sin comentarios

Convierte el código heredado en una frase sobre lo que calcula de verdad.

Prompt

def recalc(items, rate): total = 0 for it in items: if it.status != "cancelled": total += it.price * it.qty if total > 10000: total = total * (1 - rate) return round(total, 2)# lo heredé del desarrollador anterior, no hay ni un comentario — explica qué calcula esta función y en qué condiciones

Resultado

La función calcula el total del pedido con descuento.1. Multiplica precio por cantidad en todas las líneas y salta las canceladas: una línea con status "cancelled" nunca entra en el total.2. Si el total supera 10000, se le aplica rate: un valor de 0.1 significa un diez por ciento menos.3. El resultado se redondea a dos decimales, es decir, a céntimos.El detalle que solo cuenta el código: el descuento se aplica al total entero, no a la parte que supera el umbral. Un pedido de 10001 acaba costando menos que uno de 9999.

Código en un lenguaje ajeno

Explica una construcción desconocida a través de la que ya usas en tu propio lenguaje.

Prompt

import functoolsdef retry(times): def decorator(fn): @functools.wraps(fn) def wrapper(*args, **kwargs): for attempt in range(times): try: return fn(*args, **kwargs) except TimeoutError: continue raise RuntimeError("all attempts failed") return wrapper return decorator# yo escribo PHP y veo un decorador por primera vez — explícamelo con conceptos que ya conozco

Resultado

Es un decorador con parámetro. Lo más parecido que ya conoces en PHP es una función que devuelve una clausura envuelta alrededor de otra función.Se lee de dentro hacia fuera:- retry(3) devuelve decorator;- decorator recibe tu función y devuelve wrapper;- la línea @retry(3) encima de una función significa exactamente fn = retry(3)(fn) — a partir de ahí el nombre apunta a wrapper.wrapper llama al original hasta tres veces y se traga TimeoutError sin decir nada; agotados los intentos, lanza RuntimeError. functools.wraps está ahí para que la envoltura conserve el nombre y el docstring del original: sin él, el depurador y cualquier traza mostrarían siempre wrapper.

Repaso línea por línea

Recorre el fragmento línea a línea, para cuando lo confuso no es el conjunto sino un punto concreto.

Prompt

def top_customers(orders, n): totals = dict() for order in orders: totals[order.customer_id] = totals.get(order.customer_id, 0) + order.total ranked = sorted(totals.items(), key=lambda pair: -pair[1]) return ranked[:n]# repásamelo línea por línea, sobre todo la de sorted y el signo menos delante de pair

Resultado

1. totals = dict() — un diccionario vacío donde se acumulan los importes por cliente.2. El bucle recorre los pedidos. totals.get(id, 0) devuelve lo acumulado o cero si el cliente aparece por primera vez, y por eso no hace falta comprobar aparte el primer pedido.3. sorted sobre items() ordena pares de cliente e importe. El menos delante de pair[1] invierte el orden: en vez de ascendente, descendente, así los clientes más grandes quedan arriba.4. ranked[:n] se queda con los n primeros pares.Devuelve los pares y no solo los identificadores a propósito: quien llama necesita el importe para mostrarlo y así no tiene que volver a sumarlo.

Por qué está escrito así

Explica no lo que hace el código, sino por qué su autor eligió esta forma.

Prompt

_cache = None_lock = threading.Lock()def get_config(): global _cache if _cache is None: with _lock: if _cache is None: _cache = load_config_from_disk() return _cache# por qué la comprobación de None aparece dos veces, antes del bloqueo y dentro de él

Resultado

Es el patrón double-checked locking, y cada comprobación está por un motivo distinto.La primera es el camino rápido. Una vez cargada la configuración, todas las llamadas siguientes ven el valor ya listo y vuelven de inmediato sin tocar el bloqueo. Tomar un bloqueo cuesta tiempo en cada llamada, y hace falta exactamente una vez en la vida del proceso.La segunda es por la carrera. Mientras el primer hilo tiene el bloqueo y lee el fichero, un segundo ya pasó la comprobación externa y espera detrás. Sin la comprobación interna volvería a cargar la configuración y pisaría el resultado del otro.La línea global es lo que hace que la asignación cambie la variable del módulo en vez de crear una local nueva.

Errores y stack traces

El texto rojo que hay que leer antes de arreglar nada: qué significa de verdad el error con el que se cae la aplicación, por qué no pasa la instalación de dependencias y qué cuenta una línea del log.

La aplicación se cae

Lee el stack trace: dónde se cortó, qué línea es la culpable y qué significa el error en sí.

Prompt

Traceback (most recent call last): File "app/orders.py", line 42, in create_order total = calc_total(items) File "app/pricing.py", line 17, in calc_total return sum(item.price * item.qty for item in items)AttributeError: 'dict' object has no attribute 'price'qué ha pasado aquí y en qué línea busco la causa

Resultado

AttributeError significa que el objeto no tiene un atributo llamado price.Una traza se lee hacia abajo para seguir la llamada y hacia arriba para encontrar la causa:- create_order, en orders.py línea 42, llamó a calc_total;- calc_total, en pricing.py línea 17, pidió item.price y se rompió.Falla la línea 17, pero el problema no está ahí. El mensaje dice que items contiene diccionarios y no objetos, y en un diccionario el acceso con punto no funciona. Es decir, quien llama pasó los datos tal cual venían del JSON sin construir un modelo. Arréglalo donde se arma items, o lee el campo como item["price"].

La instalación no pasa

Traduce el registro de instalación del idioma del gestor de paquetes al humano.

Prompt

ERROR: Could not find a version that satisfies the requirement pandas==2.9.1 (from versions: 2.0.0, 2.1.4, 2.2.2)ERROR: No matching distribution found for pandas==2.9.1no consigo instalar las dependencias, qué significa este mensaje

Resultado

Esa versión de la biblioteca no existe.pip fue al índice de paquetes, encontró pandas y enumeró las versiones disponibles: la más nueva es la 2.2.2. Tú pides la 2.9.1, que nadie publica, y de ahí el «no matching distribution».En la práctica casi siempre es una de tres cosas: una errata en el número de versión, un requirements.txt traído de un proyecto más moderno, o una versión que solo existe para otra versión de Python. La segunda línea no es un problema aparte, es la consecuencia de la primera.

Una línea del log de producción

Explica qué está informando el servidor cuando en el log aparecen un tiempo agotado y tres cifras.

Prompt

2026-09-12T03:14:07Z ERROR db pool: timeout acquiring connection after 30s, active=20 idle=0 waiting=1372026-09-12T03:14:07Z ERROR api POST /orders 503 in 30012ms request_id=8c1f2aqué me está intentando decir el servidor con estas dos líneas

Resultado

Se acabaron las conexiones a la base de datos.Primera línea: el pool esperó 30 segundos una conexión libre y se rindió. active=20 significa que las veinte están ocupadas, idle=0 que no queda ninguna libre, waiting=137 que hay ciento treinta y siete peticiones en cola.La segunda línea es la consecuencia: POST /orders estuvo colgada esos mismos 30 segundos y devolvió 503.Leído junto: llegan peticiones más rápido de lo que la base libera conexiones. El pool no está roto, está avisando honestamente de que ha tocado techo. Lo siguiente que mirar son las consultas largas que retienen una conexión y el tamaño del pool frente al número de procesos.

Expresiones regulares y comandos

Líneas que funcionan mientras nadie las toque: explicar una expresión regular trozo a trozo, saber qué valida ese patrón sacado de la configuración y qué hace el comando copiado de un manual.

Qué comprueba la expresión regular

Desmonta el patrón en partes y dice qué direcciones deja pasar y cuáles rechaza sin motivo.

Prompt

^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z][A-Za-z]+$esto está en la validación de nuestro formulario — qué comprueba exactamente y qué deja pasar de más

Resultado

El patrón comprueba la forma de una dirección, no su existencia.- ^ y $ exigen que coincida la cadena entera, no un trozo dentro de ella.- La parte anterior a la arroba: letras, cifras, punto, guion bajo, porcentaje, más y guion, al menos un carácter.- @ — exactamente una arroba.- El dominio: letras, cifras, puntos y guiones.- Al final, un punto y una extensión de dos letras como mínimo.Qué cuela de más: dos puntos seguidos, un punto al principio del nombre, un dominio que no existe. Qué rechaza sin motivo: direcciones internacionalizadas y extensiones en alfabetos no latinos. Para un formulario suele bastar, porque la existencia la confirma el correo de verificación.

Expresión sacada de la configuración

Muestra qué partes de la línea de log caen en cada grupo y en qué orden.

Prompt

^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+) - - \[([^\]]+)\] "([A-Z]+) ([^ ]+) [^"]+" ([0-9]+) ([0-9]+)$con esto se descompone una línea de access.log — qué acaba en cada grupo

Resultado

La expresión descompone una línea del log del servidor web en seis grupos.1. La dirección IP: cuatro números separados por puntos. El punto escapado significa un punto literal, no «cualquier carácter».2. La fecha entre corchetes: dentro vale cualquier carácter salvo el corchete de cierre, y eso es justo lo que impide que el grupo se coma el resto de la línea.3. El método: letras mayúsculas.4. La ruta: todo hasta el primer espacio.5. El código de respuesta.6. El tamaño de la respuesta en bytes.El orden de los grupos es el orden de los campos en el resultado: el uno es la dirección, el cinco es el estado. Una línea cuya ruta contenga un espacio no encaja entera y se salta sin más.

Comando copiado de un manual

Desmonta la cadena de la terminal antes de que pulses Enter en un servidor de producción.

Prompt

tar -czf - ./data | ssh backup@nas "cat > /backups/data-$(date +%F).tar.gz"# lo encontré en el manual de copias de seguridad — explícame qué va a pasar antes de ejecutarlo

Resultado

El comando copia la carpeta data en otra máquina sin guardar nada en esta.- tar -czf - arma el archivo y lo comprime, pero en vez de escribirlo en un fichero lo manda a la salida estándar: eso es lo que indica el guion suelto.- La barra vertical entrega ese flujo al comando siguiente.- ssh se conecta a la máquina nas con el usuario backup y ejecuta allí cat, que deja el flujo en un fichero.- $(date +%F) mete la fecha de hoy en el nombre: sale data-2026-09-13.tar.gz.El archivo no aterriza entero en ningún sitio: viaja por la red mientras se genera, así que no hace falta espacio libre en el disco de origen. Hay un filo peligroso: si ya existe un fichero con ese nombre, se sobrescribe sin avisar.

SQL y consultas a la base

Entender una consulta SQL que ocupa media pantalla: el SELECT del informe con sus JOIN explicados por partes, una función de ventana contada con un ejemplo y el punto donde la consulta tropieza.

Consulta de informe

Convierte un SELECT largo en una frase sobre qué filas acabarán en el informe.

Prompt

SELECT c.name, date_trunc('month', o.created_at) AS month, count(*) AS orders, sum(o.total) AS revenueFROM orders oJOIN customers c ON c.id = o.customer_idWHERE o.created_at >= now() - interval '1 year'GROUP BY 1, 2HAVING sum(o.total) > 1000ORDER BY revenue DESC-- explica qué devuelve esta consulta y por qué parte de los clientes no aparece en el informe

Resultado

La consulta calcula la facturación por cliente, desglosada por meses, del último año.- JOIN une el pedido con el cliente. Es un join normal, no LEFT, así que los clientes sin ningún pedido no aparecen en absoluto.- WHERE corta todo lo anterior a un año desde ahora.- date_trunc recorta la fecha al día uno del mes: eso es lo que agrupa los pedidos sueltos en bloques mensuales.- GROUP BY 1, 2 agrupa por las dos primeras expresiones de la lista: nombre del cliente y mes.- HAVING descarta los bloques por debajo de 1000. A diferencia de WHERE, este filtro se aplica cuando la suma ya está hecha.Una fila del resultado es un cliente en un mes, no un cliente entero.

Función de ventana

Explica qué calcula OVER y en qué se diferencia una ventana de una agrupación normal.

Prompt

SELECT customer_id, created_at, total, sum(total) OVER (PARTITION BY customer_id ORDER BY created_at) AS running_total, row_number() OVER (PARTITION BY customer_id ORDER BY total DESC) AS rnFROM orders-- explica qué calculan estos dos OVER y por qué las filas no se colapsan

Resultado

Una función de ventana calcula sobre un grupo sin colapsar las filas: salen tantas como entraron, solo que cada una gana dos columnas nuevas.- PARTITION BY customer_id parte la tabla en montones por cliente. Cada montón es la ventana.- En la primera columna la suma es acumulada: ORDER BY created_at fija el orden dentro de la ventana y, por defecto, se cuentan las filas desde el inicio de la ventana hasta la actual. En el tercer pedido de un cliente es la suma de los tres primeros.- El segundo OVER ordena de otra forma, de mayor a menor importe, y row_number reparte posiciones: el 1 va al pedido más grande de ese cliente.Eso es justo lo que separa una ventana de GROUP BY: la agrupación habría dejado una fila por cliente y habría perdido los pedidos individuales.

Con qué tropieza la consulta

Explica la consulta y de paso señala con qué va a chocar cuando haya datos reales.

Prompt

SELECT *FROM orders oLEFT JOIN order_items i ON i.order_id = o.idWHERE DATE(o.created_at) = '2026-09-01' AND o.status != 'cancelled'ORDER BY o.created_at DESC-- en la base de pruebas es instantánea y en producción se cuelga — explícame la consulta y con qué choca aquí

Resultado

La consulta trae los pedidos del uno de septiembre junto con todas sus líneas. LEFT JOIN significa que un pedido sin líneas también sale, con las columnas de order_items vacías.Dónde tropieza con datos reales:- DATE(o.created_at) es una función envuelta sobre la columna, y por eso el índice de created_at deja de servir: la base tiene que calcular una fecha para cada fila de la tabla. Con cien filas de prueba no se nota; con millones, es un recorrido completo.- SELECT * sobre una relación de uno a muchos devuelve cada pedido tantas veces como líneas tenga. Salen bastantes más filas que pedidos, y la aplicación tiene que volver a juntarlas.- Las dos tablas tienen columnas con el mismo nombre (id, created_at), así que en el resultado se mezclan y por el nombre ya no se sabe de qué tabla viene cada campo.

Explicar a un principiante y en una entrevista

Cuando la explicación importa más que el código: el primer programa de quien aprende a programar desde cero, un ejercicio de entrevista técnica resuelto en voz alta y el script explicado a tu jefa.

El primer programa

Una explicación para quien lleva dos semanas escribiendo código: qué hace cada línea y por qué está así.

Prompt

nums = [4, 8, 15, 16, 23, 42]total = 0for n in nums: if n % 2 == 0: total += nprint(total / len(nums))# acabo de empezar con Python — explícame paso a paso qué pasa aquí

Resultado

El programa suma los números pares de la lista y divide el resultado entre la cantidad de TODOS los números.1. nums es una lista de seis números.2. total = 0 es la caja donde vamos a ir sumando. El cero está para tener un punto de partida.3. for n in nums es el bucle: n va tomando por turnos cada número de la lista.4. n % 2 == 0 quiere decir que el resto de dividir entre dos es cero, o sea que el número es par.5. total += n añade lo encontrado a lo acumulado.6. La división entre len(nums) es entre la longitud de la lista, es decir, entre seis.Esto parece un error de planteamiento: en la lista hay cuatro números pares que suman 70, pero se divide entre seis. Si quieres la media de los pares, hay que dividir entre cuántos pares había.

Ejercicio de entrevista

El algoritmo desmenuzado como para poder contarlo en voz alta delante de una pizarra.

Prompt

def search(nums, target): lo, hi = 0, len(nums) - 1 while hi >= lo: mid = (lo + hi) // 2 if nums[mid] == target: return mid if nums[mid] > target: hi = mid - 1 else: lo = mid + 1 return -1# me lo preguntaron en una entrevista — explícame la idea del algoritmo y por qué es rápido

Resultado

Es una búsqueda binaria sobre un array ordenado. La idea es una sola: en cada paso se descarta la mitad de lo que queda.- lo y hi son los límites del tramo donde todavía puede estar la respuesta.- mid es el centro. La doble barra es división entera, por eso sale un índice y no un decimal.- Si en el centro está lo buscado, se acabó.- Si el centro es mayor, la respuesta está a la izquierda y se mueve hi.- Si no, está a la derecha y se mueve lo.- El bucle termina cuando los límites se cruzan: el valor no está y se devuelve -1.El coste es logarítmico: un millón de elementos son unos veinte pasos en lugar de un millón. La condición imprescindible es que el array ya esté ordenado; si no, el algoritmo devuelve una respuesta equivocada sin avisar.La repregunta típica en una entrevista: en lenguajes con enteros de tamaño fijo, lo + hi puede desbordar, y por eso allí se escribe lo + (hi - lo) // 2.

Explicárselo a tu jefa

El script contado sin un solo tecnicismo, para hablar con alguien que no lee código.

Prompt

import csv, smtplibrows = list(csv.DictReader(open("orders.csv")))late = [r for r in rows if r["shipped_at"] == ""]body = "Pedidos sin enviar: " + str(len(late))smtplib.SMTP("mail.local").sendmail("bot@shop.local", "ops@shop.local", body)# tengo que explicarle a una jefa no técnica qué hace este script

Resultado

El script revisa qué pedidos siguen sin enviarse y avisa por correo de cuántos son.Paso a paso, sin tecnicismos:1. Abre la tabla de pedidos: un fichero corriente que también abriría Excel.2. Se queda con las filas donde la fecha de envío está vacía, es decir, pedidos hechos pero no salidos.3. Cuenta cuántas filas así hay.4. Manda un correo a la dirección del departamento con ese número dentro.Lo que conviene que sepa una jefa: el script no cambia nada ni estorba a nadie, solo lee e informa, así que puede ejecutarse cada hora. Su punto débil es uno: lee un fichero, o sea que muestra la foto de la última exportación y no la de este momento.

Prompts para explicar código: 16 análisis listos | iBro