Habilidad de Explicador de Horarios Cron
De Wikiprompt, la enciclopedia libre de prompts
Habilidad de Explicador de Horarios Cron Una habilidad completa de IA que explica, valida y escribe horarios cron. Incluye un script de Python, referencias de sintaxis, guías de errores comunes, plantillas de revisión y ejemplos resueltos.
Contenido del PromptGuardar
🌐
---
name: cron-schedule-explainer
description: Explica, valida y escribe horarios cron - traduce una expresión cron a inglés sencillo, lista las próximas horas de ejecución y señala errores comunes como la regla de día del mes O día de la semana, fechas que nunca ocurren, huecos por cambio de hora, confusión de zonas horarias y trabajos superpuestos o demasiado frecuentes. Úsalo cuando un usuario pegue una línea crontab, un CronJob de Kubernetes, un horario de GitHub Actions, o pregunte "¿cuándo se ejecutará esto?" o "escribe un cron para cada segundo martes".
---
# Explicador de Horarios Cron
Haces que los trabajos programados sean predecibles. Para cada horario das un significado en inglés sencillo, próximas horas de ejecución concretas y los riesgos que sorprenderían a alguien a las 2 a.m.
## Archivos en esta habilidad
- `scripts/cron_explain.py` - analizador, explicador, calculador de próximas ejecuciones y verificador de errores (solo biblioteca estándar de Python 3)
- `references/cron-syntax.md` - rangos de campos, caracteres especiales, macros y diferencias entre plataformas
- `references/scheduling-pitfalls.md` - errores comunes y cómo evitarlos
- `templates/schedule-review.md` - formato de revisión
- `examples/example-backup-review.md` - una revisión trabajada de tres líneas crontab
## Flujo de trabajo
### 1. Identifica la plataforma
El cron estándar de 5 campos (Vixie cron, cronie, Kubernetes CronJob, GitHub Actions) es el predeterminado. Pregunta o verifica si el usuario quiere Quartz (6 o 7 campos con segundos y `?`), AWS EventBridge (6 campos con año) o temporizadores systemd; consulta `references/cron-syntax.md`. Nota la zona horaria: GitHub Actions siempre usa UTC; Kubernetes usa la zona horaria del controlador a menos que se establezca `timeZone`.
### 2. Ejecuta el verificador
```bash
python3 scripts/cron_explain.py "30 2 * * 1-5"
python3 scripts/cron_explain.py "0 9 1 * MON" --count 8 --from "2026-10-08 10:00"
python3 scripts/cron_explain.py --file crontab.txt
```
Imprime una explicación en inglés sencillo, las próximas N horas de ejecución (hora local ingenua del servidor) y advertencias. El código de salida es 1 cuando una expresión es inválida o nunca se ejecuta.
Si no puedes ejecutar el script, aplica las mismas reglas manualmente y dilo.
### 3. Explica
Para cada horario da:
1. Significado en inglés sencillo de una oración.
2. Las próximas 3 a 5 ejecuciones con la zona horaria indicada.
3. Advertencias del script y de `references/scheduling-pitfalls.md` que apliquen (superposición con trabajos largos, horario de verano, UTC versus local, ejecuciones perdidas mientras la máquina está apagada).
### 4. Escribe o corrige horarios
Cuando el usuario describa un horario en palabras, escribe la expresión y luego pásala por el verificador para confirmar que las próximas ejecuciones coinciden con su intención. Para cosas que cron no puede expresar directamente (cada segundo martes, el último día hábil del mes), da una expresión cron más una protección en el comando, por ejemplo `[ "$(date +\%d)" -le 07 ] && run-job`, y explica por qué.
### 5. Informa
Usa `templates/schedule-review.md`, como en `examples/example-backup-review.md`.
## Reglas
- Siempre indica la zona horaria que estás asumiendo.
- Recuerda que `%` debe escaparse como `\%` dentro de los campos de comando crontab.
- Nunca edites un crontab en vivo para el usuario; muestra la línea a añadir y el paso `crontab -e`.
- Recomienda un bloqueo (por ejemplo `flock -n /tmp/job.lock cmd`) siempre que un trabajo pueda ejecutarse más tiempo que su intervalo.
FILE:references/cron-syntax.md
# Referencia de Sintaxis Cron (estándar de 5 campos)
```
+------------- minuto (0-59)
| +----------- hora (0-23)
| | +--------- día del mes (1-31)
| | | +------- mes (1-12 o ENE-DIC)
| | | | +----- día de la semana (0-7 o DOM-SAB; 0 y 7 son ambos domingo)
| | | | |
* * * * * comando
```
## Caracteres especiales
| Símbolo | Significado | Ejemplo |
|---|---|---|
| `*` | cada valor | `* * * * *` cada minuto |
| `,` | lista | `0 8,12,18 * * *` a las 08:00, 12:00, 18:00 |
| `-` | rango | `0 9 * * 1-5` 09:00 de lunes a viernes |
| `/` | paso | `*/15 * * * *` cada 15 minutos; `10-50/20` = 10, 30, 50 |
Los nombres no distinguen mayúsculas de minúsculas. Los rangos de nombres (`LUN-VIE`) funcionan en la mayoría de implementaciones, las listas de nombres funcionan en todas partes.
## Macros
| Macro | Equivalente |
|---|---|
| `@yearly` / `@annually` | `0 0 1 1 *` |
| `@monthly` | `0 0 1 * *` |
| `@weekly` | `0 0 * * 0` |
| `@daily` / `@midnight` | `0 0 * * *` |
| `@hourly` | `0 * * * *` |
| `@reboot` | una vez al inicio (no basado en tiempo) |
## La regla del día
Si tanto el día del mes como el día de la semana están restringidos (ninguno es `*`), el trabajo se ejecuta cuando COINCIDE cualquiera. `0 9 1 * MON` se ejecuta el 1 de cada mes Y cada lunes.
## Diferencias entre plataformas
| Plataforma | Campos | Zona horaria | Notas |
|---|---|---|---|
| Cron de Linux (cronie, Vixie) | 5 | hora local del sistema | `CRON_TZ=` soportado por cronie |
| Kubernetes CronJob | 5 | zona horaria del controlador, o `spec.timeZone` | usa `concurrencyPolicy: Forbid` para prevenir superposiciones |
| `schedule` de GitHub Actions | 5 | siempre UTC | las ejecuciones pueden retrasarse bajo carga; intervalo mínimo 5 minutos |
| Quartz (Java) | 6-7 (segundos primero, año opcional) | configurable | `?` para "sin valor específico", `L`, `W`, `#` soportados |
| AWS EventBridge | 6 (con año) | UTC a menos que se establezca una zona horaria de programador | o el día del mes o el día de la semana debe ser `?` |
El script en esta habilidad soporta el estándar de 5 campos más las macros anteriores (excepto `@reboot`, que reporta como no basado en tiempo).
FILE:references/scheduling-pitfalls.md
# Errores Comunes de Programación
## 1. Día del mes O día de la semana
`0 0 13 * 5` NO es "viernes 13". Se ejecuta cada 13 y cada viernes. Usa `0 0 13 * *` más una protección: `[ "$(date +\%u)" = 5 ] && cmd`.
## 2. Fechas que nunca o raramente ocurren
- `0 0 30 2 *` nunca se ejecuta (febrero no tiene 30).
- `0 0 31 * *` se ejecuta solo en 7 meses del año.
- `0 0 29 2 *` se ejecuta solo en años bisiestos.
Para "último día del mes" usa `0 0 28-31 * *` con una protección: `[ "$(date -d tomorrow +\%d)" = 01 ] && cmd`.
## 3. Horario de verano
En zonas horarias locales con horario de verano, las horas entre aproximadamente 01:00 y 03:00 pueden omitirse (adelanto de primavera) o ejecutarse dos veces (retroceso de otoño), dependiendo de la implementación de cron. Programa trabajos críticos fuera de esa ventana, o ejecuta cron en UTC.
## 4. UTC versus hora local
GitHub Actions y muchos programadores en la nube usan UTC. "Cada día a las 09:00" para un equipo en Estambul (UTC+3) es `0 6 * * *` en UTC. Siempre escribe la zona horaria junto a la expresión en documentos y comentarios de código.
## 5. Ejecuciones demasiado frecuentes o superpuestas
- `* * * * *` se ejecuta 1440 veces al día. Asegúrate de que eso es lo que se quiere.
- Un campo de minutos `*` con una hora fija (`* 3 * * *`) se ejecuta 60 veces entre las 03:00 y las 03:59; normalmente se quería `0 3 * * *`.
- Si un trabajo puede tardar más que su intervalo, usa un bloqueo (`flock -n`) o `concurrencyPolicy: Forbid`.
## 6. Los valores de paso no se envuelven uniformemente
`*/7` en el campo de minutos se ejecuta a las 0, 7, ..., 56, y luego de nuevo a las 0 (un hueco de 4 minutos). `*/25` se ejecuta a las 0, 25, 50. Los pasos se reinician cada hora, día o mes.
## 7. Manada atronadora
Muchos equipos eligen `0 0 * * *` o `0 * * * *`. Cambia los trabajos a un minuto impar (por ejemplo `17 2 * * *`) para evitar picos de carga en sistemas compartidos y APIs con límite de tasa.
## 8. Entorno y salida
Cron se ejecuta con un PATH mínimo y sin shell de inicio de sesión. Usa rutas absolutas, establece las variables necesarias en el crontab y redirige la salida (`>> /var/log/job.log 2>&1`) para que los fallos sean visibles. Escapa `%` como `\%`.
## 9. Ejecuciones perdidas
El cron simple no se pone al día con ejecuciones perdidas mientras la máquina estaba apagada. Usa anacron, temporizadores systemd con `Persistent=true`, o `startingDeadlineSeconds` de Kubernetes cuando una ejecución perdida importe.
FILE:templates/schedule-review.md
# Revisión de Horarios: {{system_or_repo}}
**Plataforma:** {{cron de Linux | Kubernetes CronJob | GitHub Actions | otro}}
**Zona horaria asumida:** {{time_zone}}
**Revisado el:** {{date}}
## Resumen
{{Una o dos oraciones: ¿están los horarios haciendo lo que el equipo espera, y qué debe cambiar?}}
## Horarios
### {{n}}. `{{expression}}` - {{nombre del trabajo}}
- **Significado:** {{explicación en inglés sencillo}}
- **Próximas ejecuciones:** {{ejecución 1}}, {{ejecución 2}}, {{ejecución 3}}
- **Veredicto:** {{OK | CORREGIR | ACLARAR}}
- **Advertencias:**
- {{advertencia}}
- **Línea sugerida:**
```
{{línea crontab corregida}}
```
## Preguntas
- {{pregunta para el equipo}}
FILE:examples/example-backup-review.md
# Revisión de Horarios: crontab del servidor de operaciones
**Plataforma:** cron de Linux (cronie)
**Zona horaria asumida:** Europa/Berlín (hora local del servidor, tiene horario de verano)
**Revisado el:** 2026-10-08
## Resumen
Dos de las tres líneas no hacen lo que dicen los comentarios. La copia de seguridad se ejecuta dentro de la ventana de horario de verano, y el informe del "viernes 13" en realidad se ejecuta cada viernes y cada 13.
## Horarios
### 1. `30 2 * * *` - copia de seguridad nocturna de la base de datos
- **Significado:** A las 02:30 cada día.
- **Próximas ejecuciones:** 2026-10-09 02:30, 2026-10-10 02:30, 2026-10-11 02:30
- **Veredicto:** CORREGIR
- **Advertencias:**
- Las 02:30 está dentro de la ventana de cambio de horario de verano; en la noche de adelanto de primavera puede omitirse y en otoño puede ejecutarse dos veces.
- La copia de seguridad puede tardar más de una hora a fin de mes; sin bloqueo.
- **Línea sugerida:**
```
17 4 * * * flock -n /tmp/db-backup.lock /opt/scripts/db-backup.sh >> /var/log/db-backup.log 2>&1
```
### 2. `0 9 13 * FRI` - informe divertido del "viernes 13"
- **Significado:** A las 09:00 en el día 13 del mes O cada viernes (la regla del día de cron).
- **Próximas ejecuciones:** 2026-10-09 09:00 (vie), 2026-10-13 09:00 (mar, el 13), 2026-10-16 09:00 (vie)
- **Veredicto:** CORREGIR
- **Advertencias:**
- Ambos campos de día están restringidos, así que cron usa O, no Y.
- **Línea sugerida:**
```
0 9 13 * * [ "$(date +\%u)" = 5 ] && /opt/scripts/fun-report.sh
```
### 3. `*/20 8-18 * * 1-5` - sincronizar tickets del servicio de asistencia
- **Significado:** Cada 20 minutos (a las :00, :20, :40) de 08:00 a 18:59, de lunes a viernes.
- **Próximas ejecuciones:** 2026-10-08 10:20, 2026-10-08 10:40, 2026-10-08 11:00
- **Veredicto:** ACLARAR
- **Advertencias:**
- La última ejecución del día es a las 18:40, no a las 18:00. Usa `8-17` más un `0 18 * * 1-5` separado si la sincronización debe detenerse a las 18:00.
## Preguntas
- ¿Debería el servidor ejecutar cron en UTC para evitar problemas de horario de verano por completo?
FILE:scripts/cron_explain.py
#!/usr/bin/env python3
"""Explica, valida y previsualiza expresiones cron estándar de 5 campos (solo stdlib).
Uso:
python3 cron_explain.py "EXPR" [--count N] [--from "YYYY-MM-DD HH:MM"]
python3 cron_explain.py --file crontab.txt [--count N] [--from ...]
Para cada expresión: una explicación en inglés sencillo, las próximas N horas de ejecución
(hora local ingenua del servidor) y advertencias de errores comunes. En modo --file, se leen
líneas crontab; los comentarios, líneas en blanco y líneas VAR=valor se omiten y los
primeros cinco campos (o una macro @ inicial) se toman como el horario.
Código de salida: 0 = todo válido, 1 = una expresión es inválida o nunca se ejecuta, 2 = uso.
"""
import argparse
import calendar
import datetime as dt
import sys
MONTHS = {m.lower(): i for i, m in enumerate(calendar.month_abbr) if m}
DAYS = {"dom": 0, "lun": 1, "mar": 2, "mié": 3, "jue": 4, "vie": 5, "sáb": 6}
MACROS = {
"@yearly": "0 0 1 1 *", "@annually": "0 0 1 1 *", "@monthly": "0 0 1 * *",
"@weekly": "0 0 * * 0", "@daily": "0 0 * * *", "@midnight": "0 0 * * *",
"@hourly": "0 * * * *",
}
FIELDS = [("minuto", 0, 59, {}), ("hora", 0, 23, {}), ("día del mes", 1, 31, {}),
("mes", 1, 12, MONTHS), ("día de la semana", 0, 7, DAYS)]
DAY_NAMES = ["Domingo", "Lunes", "Martes", "Miércoles", "Jueves", "Viernes", "Sábado"]
def compress(values, fmt=str):
"""[1,2,3,5] -> '1-3, 5' usando fmt para cada número."""
vals, out, i = sorted(values), [], 0
while i < len(vals):
j = i
while j + 1 < len(vals) and vals[j + 1] == vals[j] + 1:
j += 1
out.append(fmt(vals[i]) if j - i < 2 else f"{fmt(vals[i])}-{fmt(vals[j])}")
if 0 < j - i < 2:
out.append(fmt(vals[j]))
i = j + 1
return ", ".join(out)
class CronError(ValueError):
pass
def _num(token, lo, hi, names, field):
t = token.lower()
if t in names:
return names[t]
if not t.isdigit():
raise CronError(f"{field}: '{token}' no es un número o nombre conocido")
v = int(t)
if not lo <= v <= hi:
raise CronError(f"{field}: {v} está fuera de {lo}-{hi}")
return v
def parse_field(text, lo, hi, names, field):
values = set()
for part in text.split(","):
if not part:
raise CronError(f"{field}: elemento de lista vacío en '{text}'")
step = 1
if "/" in part:
part, step_s = part.split("/", 1)
if not step_s.isdigit() or int(step_s) == 0:
raise CronError(f"{field}: paso malo '/{step_s}'")
step = int(step_s)
if part == "*":
start, end = lo, hi
elif "-" in part:
a, b = part.split("-", 1)
start, end = _num(a, lo, hi, names, field), _num(b, lo, hi, names, field)
if start > end:
raise CronError(f"{field}: rango {a}-{b} está invertido")
else:
start = _num(part, lo, hi, names, field)
end = hi if step > 1 else start
values.update(range(start, end + 1, step))
return values
def parse(expr):
expr = expr.strip()
if expr.lower() == "@reboot":
raise CronError("@reboot se ejecuta una vez al inicio y no está basado en tiempo")
expr = MACROS.get(expr.lower(), expr)
parts = expr.split()
if len(parts) != 5:
hint = " (6-7 campos parecen Quartz o EventBridge; consulta references/cron-syntax.md)" if len(parts) in (6, 7) else ""
raise CronError(f"se esperaban 5 campos, se obtuvieron {len(parts)}{hint}")
for p in parts:
bare = p.lower()
for n in list(MONTHS) + list(DAYS):
bare = bare.replace(n, "")
if any(c in bare for c in "?lw#"):
raise CronError(f"'{p}': '?', 'L', 'W' y '#' son extensiones de Quartz, no cron estándar")
sets = [parse_field(p, lo, hi, names, name) for p, (name, lo, hi, names) in zip(parts, FIELDS)]
if 7 in sets[4]:
sets[4].discard(7)
sets[4].add(0)
return parts, sets
def describe_set(values, lo, hi, field, raw):
vals = sorted(values)
if raw == "*":
return None
if field == "día de la semana":
names = [DAY_NAMES[v] for v in vals]
if vals == [1, 2, 3, 4, 5]:
return "de lunes a viernes"
if vals == [0, 6]:
return "los fines de semana"
return ", ".join(names)
if field == "mes":
return ", ".join(calendar.month_name[v] for v in vals)
return compress(vals)
def explain(parts, sets):
minute, hour, dom, month, dow = sets
rm, rh, rdom, rmon, rdow = parts
if rm == "*" and rh == "*":
time_txt = "cada minuto"
elif rm.startswith("*/") and rh == "*":
time_txt = f"cada {rm[2:]} minutos"
elif rm.startswith("*/"):
time_txt = (f"cada {rm[2:]} minutos (en el minuto " + ", ".join(str(m) for m in sorted(minute)) +
") durante la(s) hora(s) " + compress(hour, lambda h: f"{h:02d}"))
elif rh == "*":
time_txt = "en el minuto " + ", ".join(str(m) for m in sorted(minute)) + " de cada hora"
elif rm == "*":
time_txt = "cada minuto durante la(s) hora(s) " + compress(hour, lambda h: f"{h:02d}")
elif len(minute) * len(hour) <= 6:
time_txt = "a las " + ", ".join(f"{h:02d}:{m:02d}" for h in sorted(hour) for m in sorted(minute))
else:
time_txt = ("en el/los minuto(s) " + ", ".join(str(m) for m in sorted(minute)) +
" pasada(s) la(s) hora(s) " + compress(hour, lambda h: f"{h:02d}"))
day_txt = []
d_dom = describe_set(dom, 1, 31, "día del mes", rdom)
d_dow = describe_set(dow, 0, 6, "día de la semana", rdow)
if d_dom and d_dow:
day_txt.append(f"en el/los día(s) {d_dom} del mes O en {d_dow}")
elif d_dom:
day_txt.append(f"en el/los día(s) {d_dom} del mes")
elif d_dow:
day_txt.append(d_dow if d_dow.startswith("en ") else f"en {d_dow}")
else:
day_txt.append("cada día")
d_mon = describe_set(month, 1, 12, "mes", rmon)
if d_mon:
day_txt.append(f"en {d_mon}")
text = f"{time_txt}, {' '.join(day_txt)}"
return text[0].upper() + text[1:] + "."
def day_matches(d, parts, sets):
_, _, dom, month, dow = sets
if d.month not in month:
return False
cron_dow = (d.weekday() + 1) % 7
dom_r, dow_r = parts[2] != "*", parts[4] != "*"
if dom_r and dow_r:
return d.day in dom or cron_dow in dow
if dom_r:
return d.day in dom
if dow_r:
return cron_dow in dow
return True
def next_runs(parts, sets, start, count, max_days=366 * 8):
minute, hour = sorted(sets[0]), sorted(sets[1])
runs = []
day = start.date()
for _ in range(max_days):
if day_matches(day, parts, sets):
for h in hour:
for m in minute:
t = dt.datetime(day.year, day.month, day.day, h, m)
if t > start:
runs.append(t)
if len(runs) >= count:
return runs
day += dt.timedelta(days=1)
return runs
def warnings(parts, sets, runs):
minute, hour, dom, month, dow = sets
out = []
if parts[2] != "*" and parts[4] != "*":
out.append("El día del mes Y el día de la semana están ambos establecidos: cron se ejecuta cuando COINCIDE cualquiera (O, no Y).")
if parts[2] != "*" and parts[4] == "*":
max_days = {m: (29 if m == 2 else calendar.monthrange(2026, m)[1]) for m in month}
if not any(d <= max_days[m] for m in month for d in dom):
out.append("Nunca se ejecuta: el/los día(s) elegido(s) del mes no existen en el/los mes(es) elegido(s).")
elif any(d > 28 for d in dom):
out.append("Algunos días elegidos (29-31) no existen en todos los meses, así que algunos meses se omiten.")
if parts[0] == "*" and parts[1] != "*":
out.append("El minuto es '*': se ejecuta cada minuto de la(s) hora(s) elegida(s); ¿querías decir minuto 0?")
runs_per_day = len(minute) * len(hour)
if runs_per_day >= 288:
out.append(f"Se ejecuta {runs_per_day} veces al día; asegúrate de que eso es lo que se quiere y añade un bloqueo contra superposiciones.")
if any(1 <= h <= 2 for h in hour) and parts[1] != "*":
out.append("Se ejecuta entre las 01:00 y las 02:59: en zonas horarias locales con horario de verano esto puede omitirse o ejecutarse dos veces.")
for i, raw in ((0, parts[0]), (1, parts[1])):
if "/" in raw:
step = int(raw.split("/")[1])
span = 60 if i == 0 else 24
if span % step:
out.append(f"El paso /{step} no divide {span}: el hueco es desigual donde la {'hora' if i == 0 else 'día'} se envuelve.")
if parts[0] == "0" and parts[1] in ("*", "0"):
out.append("El minuto 0 en punto es un espacio popular; considera un minuto impar para evitar picos de carga.")
return out
def check(expr, start, count):
print(f"Expresión: {expr}")
try:
parts, sets = parse(expr)
except CronError as e:
print(f" INVÁLIDA: {e}\n")
return False
print(f" Significado: {explain(parts, sets)}")
runs = next_runs(parts, sets, start, count)
ok = True
if runs:
print(f" Próximas {len(runs)} ejecución(es) después de {start:%Y-%m-%d %H:%M} (hora local del servidor):")
for r in runs:
print(f" {r:%Y-%m-%d %H:%M} {r:%a}")
else:
print(" Próximas ejecuciones: ninguna encontrada en los próximos 8 años")
ok = False
for w in warnings(parts, sets, runs):
print(f" ADVERTENCIA: {w}")
print()
return ok
def crontab_schedules(path):
with open(path, encoding="utf-8") as f:
for line in f:
s = line.strip()
if not s or s.startswith("#"):
continue
first = s.split()[0]
if "=" in first and not first.startswith("@"):
continue
yield first if first.startswith("@") else " ".join(s.split()[:5])
def main(argv=None):
ap = argparse.ArgumentParser(description="Explica y valida expresiones cron.")
ap.add_argument("expr", nargs="?", help='expresión cron entre comillas, p.ej. "*/15 9-17 * * 1-5"')
ap.add_argument("--file", help="lee horarios de un archivo crontab")
ap.add_argument("--count", type=int, default=5, help="número de próximas ejecuciones a mostrar (predeterminado 5)")
ap.add_argument("--from", dest="start", help='hora de inicio "YYYY-MM-DD HH:MM" (predeterminado: ahora)')
a = ap.parse_args(argv)
if bool(a.expr) == bool(a.file):
ap.print_usage(sys.stderr)
print("error: da exactamente uno de EXPR o --file", file=sys.stderr)
return 2
try:
start = dt.datetime.strptime(a.start, "%Y-%m-%d %H:%M") if a.start else dt.datetime.now().replace(second=0, microsecond=0)
except ValueError:
print("error: --from debe verse como 2026-10-08 10:00", file=sys.stderr)
return 2
exprs = list(crontab_schedules(a.file)) if a.file else [a.expr]
results = [check(e, start, max(1, a.count)) for e in exprs]
print(f"{sum(results)} de {len(results)} horario(s) válido(s) y ejecutable(s).")
return 0 if all(results) else 1
if __name__ == "__main__":
sys.exit(main())
Iniciá sesión para ver el prompt completo
Continuar con:
Al iniciar sesión, aceptás nuestros Términos de uso y Política de privacidad
Uso
Este prompt está diseñado para usarse con productivity. Copiá el contenido de arriba y pegalo en tu herramienta de IA preferida.
Para mejores resultados, personalizá los marcadores (indicados con corchetes o mayúsculas) con tus requisitos específicos.
Discusión
0 comentarios