El auditor que nunca se deja engañar
En la guía anterior usaste una segunda llamada a DeepSeek como auditor. Funciona, pero sigue siendo una IA opinando sobre otra IA. Aquí vas a construir la versión que usamos nosotros en producción: un auditor 100% código — sin IA, sin opinión, sin posibilidad de que le "cuelen" una respuesta a medias. Es el mismo patrón con el que estamos resolviendo miles de tareas reales ahora mismo.
Antes de empezar: este recurso da por hecho que ya hiciste Loops: haz que la IA trabaje sola — mismo servidor, mismo concepto DAME-V, mismo modelo DeepSeek V4. Aquí solo cambia el auditor.
Por qué un auditor de código gana a un auditor de IA
Cuando el auditor es otra llamada a DeepSeek, sigue siendo un modelo de lenguaje "opinando" — puede aprobar algo que suena bien pero está mal, o rechazar algo correcto por capricho. Cuando el criterio se puede escribir como código (un formato, una cantidad de caracteres, que un dato no cambió, que un número cuadra), el código no opina, verifica. Es la diferencia entre preguntarle a alguien "¿esto se ve bien?" y medirlo con una regla.
Auditor con IA (guía anterior)
Otra llamada a DeepSeek revisa el resultado. Mejor que nada, pero puede alucinar su propio veredicto — un "SI" cuando debería decir "NO".
Auditor determinista (este recurso)
Una función en Python que comprueba: ¿el formato es correcto? ¿los datos originales siguen intactos? ¿no se inventó nada? Sin IA de por medio — no se puede convencer.
| Tarea | Normalizar 10 números de teléfono mexicanos a formato +52XXXXXXXXXX |
| Resultado | 8 de 10 resueltos correctamente (verificados a mano, uno por uno) |
| Bloqueados | 2 — el auditor los rechazó porque no podía descartar que la IA hubiera inventado dígitos, no porque "se viera mal" |
| Costo real | $0.002 USD · 14 llamadas a la API |
La plantilla: 4 piezas que tú defines, el resto ya está hecho
Vas a copiar un script en Python con 4 funciones marcadas para editar. Todo lo demás — presupuesto, reintentos, subir de modelo si el barato falla, cola resumible, freno de emergencia, resumen final — ya viene armado y no hace falta tocarlo.
build_plan()— arma la lista de cosas por hacer.build_prompt(item, problemas)— el texto que le mandas a DeepSeek, incluyendo por qué falló la vez anterior (si falló).validate(item, propuesta)— tu auditor. Revisa con código, nunca con IA, si la propuesta cumple.apply(item, propuesta)— cómo guardas el resultado ya aprobado.
a) El ejemplo completo — normalizar teléfonos (el que probamos arriba)
Entra a la terminal de tu servidor y crea el archivo:
$ mkdir -p ~/loops/telefonos && cd ~/loops/telefonos
$ nano loop.py
Pega esto — reemplaza TU_CLAVE_AQUI por tu clave de DeepSeek y la lista DATOS por tus propios registros:
import json, os, re, sys, time, urllib.request
from datetime import datetime
from pathlib import Path
API_KEY = "TU_CLAVE_AQUI"
MODELO_BARATO, MODELO_POTENTE = "deepseek-v4-flash", "deepseek-v4-pro"
MAX_TRIES = 3
MAX_USD = 1.00 # tope duro de gasto — el script se detiene solo
PRECIOS = { # USD por token (entrada, salida) — verificados jul 2026
MODELO_BARATO: (0.14/1e6, 0.28/1e6),
MODELO_POTENTE: (0.435/1e6, 0.87/1e6),
}
STATE = Path(__file__).parent
PLAN, BUDGET, PROG = STATE/"plan.json", STATE/"budget.json", STATE/"progreso.log"
def jload(p, d):
return json.loads(p.read_text()) if p.exists() else d
def jsave(p, o):
p.write_text(json.dumps(o, ensure_ascii=False, indent=1))
def log(linea):
with PROG.open("a") as f: f.write(f"{datetime.now():%H:%M:%S} {linea}\n")
print(linea)
def llamar_deepseek(mensajes, modelo):
body = {"model": modelo, "messages": mensajes, "temperature": 0.2}
req = urllib.request.Request("https://api.deepseek.com/v1/chat/completions",
data=json.dumps(body).encode(), method="POST",
headers={"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=120) as r:
d = json.loads(r.read())
return d["choices"][0]["message"]["content"], d.get("usage", {})
# --- tus datos: pon aquí lo que quieras normalizar ---
DATOS = ["33 1234 5678", "(55) 8765-4321", "044-33-1234-5678"]
def build_plan():
return [{"id": f"tel-{i}", "estado": "open", "intentos": 0, "raw": r}
for i, r in enumerate(DATOS, 1)]
def build_prompt(item, problemas):
extra = ("\nFalló antes por: " + "; ".join(problemas)) if problemas else ""
return [{"role": "system", "content":
"Normaliza a +52 + 10 dígitos. Responde SOLO JSON: {\"telefono\": \"+52XXXXXXXXXX\"}"},
{"role": "user", "content": f"Texto: {item['raw']}{extra}"}]
def validate(item, propuesta):
# --- ESTE es tu auditor determinista: código, cero IA ---
try:
tel = json.loads(propuesta[propuesta.find("{"):propuesta.rfind("}")+1])["telefono"]
except Exception:
return False, ["JSON inválido"]
if not re.fullmatch(r"\+52\d{10}", tel):
return False, [f"formato incorrecto: {tel}"]
original = re.sub(r"\D", "", item["raw"])
if tel[3:] not in original: # anti-alucinación: no inventó dígitos
return False, ["dígitos no coinciden con el original"]
return True, []
def apply(item, propuesta):
tel = json.loads(propuesta[propuesta.find("{"):propuesta.rfind("}")+1])["telefono"]
item["normalizado"] = tel
log(f"GUARDADO {item['id']}: {item['raw']} -> {tel}")
# --- el ciclo (genérico, no hace falta tocarlo) ---
budget = jload(BUDGET, {"usd": 0.0})
plan = jload(PLAN, None) or build_plan()
for u in plan:
if u["estado"] != "open": continue
if (STATE/"STOP").exists() or budget["usd"] >= MAX_USD:
log("FRENO activado (archivo STOP o tope de gasto) — deteniendo"); break
problemas, ok = [], False
for intento in range(1, MAX_TRIES+1):
modelo = MODELO_POTENTE if intento == MAX_TRIES else MODELO_BARATO
try:
texto, uso = llamar_deepseek(build_prompt(u, problemas), modelo)
except Exception as e: # un fallo de internet NO tumba el loop
log(f"{u['id']} error de conexión: {e} — reintento en 10s")
time.sleep(10); continue
p_in, p_out = PRECIOS[modelo]
budget["usd"] += uso.get("prompt_tokens", 0)*p_in + uso.get("completion_tokens", 0)*p_out
jsave(BUDGET, budget)
ok, problemas = validate(u, texto)
if ok: break
log(f"{u['id']} intento {intento} rechazado: {problemas}")
u["estado"] = "done" if ok else "blocked"
u["intentos"] = intento
if ok: apply(u, texto)
jsave(PLAN, plan)
log(f"FIN — ${budget['usd']:.4f} gastados")
Fíjate en el truco del auditor: tel[3:] not in original comprueba que los 10 dígitos que propuso la IA de verdad estaban en el texto original — así la IA no puede "inventar" un número que suene válido. Ese tipo de comprobación (algo que ya sabías del dato original, y que el resultado debe seguir cumpliendo) es la clave de cualquier buen auditor determinista.
b) Corre tu loop
$ python3 loop.py
$ cat progreso.log # qué pasó, uno por uno
$ cat plan.json # qué quedó done y qué quedó blocked
¿Necesitas frenarlo a mitad de corrida? Crea un archivo llamado STOP en la misma carpeta (touch STOP) — el loop se detiene solo antes de la siguiente tarea, igual que en la guía anterior. Bórralo cuando quieras volver a correr.
Cómo lo adaptas a TU tarea
Este mismo esqueleto sirve para cualquier trabajo repetitivo, no solo teléfonos. Solo cambia las 4 funciones. Algunas ideas de auditor determinista según la tarea:
- Limpiar nombres de clientes: el auditor comprueba que ninguna palabra clave del nombre original desapareció.
- Clasificar mensajes por categoría: el auditor comprueba que la categoría devuelta está en tu lista cerrada de categorías válidas — nada inventado.
- Extraer un monto de un texto: el auditor comprueba que el número extraído aparece literalmente en el texto original.
- Convertir un formato de archivo a otro: el auditor cuenta filas/campos antes y después — deben coincidir.
La pregunta que te tienes que hacer siempre: "¿qué del dato original DEBE seguir siendo cierto en el resultado?" Esa es tu regla de auditor.
Listo. Ya sabes construir el nivel pro 🛡️
Un auditor de código no se cansa, no se deja convencer y nunca aprueba "más o menos bien".
Volver a mis recursos