SOP — Agente de Activación de Clientes en Discord con Claude Code


Sistema automatizado para reactivar clientes inactivos sin quemar la relación


Versión 1.0 · Abril 2026 · Autor: Darío Porta Sánchez · HTO Consulting LLC


────────────────────────────────


Índice


* Prefacio — qué es, qué no es, para quién
* El problema real — por qué los clientes se apagan
* El framework S.P.A.R.K. — reactivación sin parecer vendedor desesperado
* Arquitectura del sistema — visión aérea
* Prerrequisitos técnicos
* Setup paso a paso — Discord Bot
* Setup paso a paso — Claude Code y estructura de proyecto
* Memoria por cliente — el cerebro del agente
* El Mega Prompt — corazón del sistema
* Detección de inactividad — lógica de scoring
* Scheduling con launchd en Mac
* Brainstorming de tipos de mensaje
* Reglas duras y anti-patterns
* Métricas y evolución del agente
* Troubleshooting
* Anexo A — código completo
* Anexo B — launchd plist listo para usar
* Anexo C — checklist pre-producción


────────────────────────────────


1. Prefacio — qué es, qué no es, para quién


Qué es


Un agente autónomo, corriendo en tu Mac, que dos veces al día:


* Lee todos los canales privados de clientes en tu Discord
* Detecta qué clientes llevan X días sin escribir
* Lee los últimos mensajes de cada cliente inactivo para entender contexto
* Consulta una memoria por cliente que se va enriqueciendo con cada interacción
* Genera un mensaje de reactivación personalizado usando el framework S.P.A.R.K.
* Envía ese mensaje automáticamente al canal privado del cliente
* Registra la acción en logs y actualiza la memoria del cliente


Todo corriendo en local, sin Zapier, sin SaaS de retention, sin tocar tu CRM.


Qué NO es


* **No es un blaster.** No manda el mismo mensaje a todos. Cada mensaje está calibrado al contexto del cliente específico.
* **No es un chatbot conversacional.** No responde a mensajes entrantes. Su único trabajo es iniciar conversación cuando el cliente se apaga.
* **No es un sustituto de la relación humana.** Es un sistema de "primer toque" que devuelve la pelota al cliente. Cuando el cliente responde, tú (o tu CSM) tomas control.
* **No es legal spam.** El agente solo escribe a clientes activos de tu programa en canales donde ya existe relación previa.


Para quién


* Consultores, coaches, operators que tienen un Discord privado con clientes
* Cada cliente tiene su propio canal privado (o un DM abierto con el bot)
* Volumen entre 5 y 200 clientes activos simultáneamente
* Background técnico medio: sabes qué es un bot token, has usado terminal, has tocado APIs alguna vez


Si nunca has abierto Discord Developer Portal, este SOP te lleva paso a paso. Pero asume que no le tienes miedo a copiar comandos.


────────────────────────────────


2. El problema real — por qué los clientes se apagan


La mayoría de consultores interpreta el silencio del cliente como desinterés. Error. El silencio es casi siempre una de estas cuatro cosas:


Los 4 tipos de silencio


1. Silencio por ejecución (el sano)
El cliente está implementando lo que le dijiste. Está cabeza abajo haciendo el trabajo. No necesita hablar contigo, necesita ejecutar. Este cliente, si le escribes mal, lo interrumpes. Si le escribes bien, le das dopamina sin sacarlo de flow.


2. Silencio por bloqueo (el peligroso)
El cliente no está ejecutando. Está atascado con algo técnico, emocional o estratégico y no te lo dice. La vergüenza es la razón #1 de churn silencioso. Si no interrumpes este silencio, el cliente desaparece en 30-60 días.


3. Silencio por duda (el crítico)
El cliente está cuestionando si esto funciona. Probablemente ha visto contenido de otro "gurú" que le hizo dudar de tu método. Si no intervienes, re-contrata a otro o se va.


4. Silencio por vida (el humano)
Mudanza, salud, familia, crisis. No tiene nada que ver contigo. Aquí un mensaje mal calibrado es ofensivo. Un mensaje bien calibrado te convierte en alguien memorable.


Por qué fallan las reactivaciones típicas


El 90% de los consultores reactiva así:


> "Hey [nombre], ¿cómo vas? ¿Alguna duda?"


Esto es basura por tres razones:


* **Pregunta de bajo contexto.** Demuestra que no has estado prestando atención.
* **Fricción alta para responder.** "¿Cómo voy?" exige resumir 2 semanas de trabajo. Nadie tiene ganas.
* **Cero dopamina.** No hay novedad, no hay juego, no hay chispa. Es admin.


El framework S.P.A.R.K. elimina estos tres errores por diseño.


────────────────────────────────


3. El framework S.P.A.R.K.


Cinco componentes, cada uno activa un mecanismo neurológico distinto. La combinación multiplica la probabilidad de respuesta.


S — Señal


Referencia algo específico y reciente del cliente. Algo que demuestra que le has estado prestando atención.


* Un mensaje concreto que escribió hace 2 semanas
* Un objetivo que definió en onboarding
* Un obstáculo que mencionó en una llamada
* Un win pequeño que compartió


Ejemplo malo: "¿Cómo va todo?"
Ejemplo bueno: "Hace 15 días dijiste que ibas a probar el hook de 'el error que cuesta millones' en la campaña de España. ¿Lo llegaste a lanzar?"


Mecanismo neurológico: validación social + sensación de ser visto. Activa oxitocina.


P — Patrón interrumpido


No abras con el saludo típico. Rompe expectativa.


* Observación inesperada
* Confesión breve
* Pregunta filosa
* Declaración contra-intuitiva


Ejemplo malo: "¡Hola! Espero que estés bien."
Ejemplo bueno: "Tengo una teoría sobre por qué llevas callado dos semanas, y quiero que me digas si acierto."


Mecanismo neurológico: novelty detection. Activa dopamina anticipatoria.


A — Anclaje al outcome


Conecta el mensaje con lo que el cliente vino a buscar. No con tu servicio, no con la industria, con SU objetivo original.


* El número que quería alcanzar
* La libertad que quería comprar
* El problema que quería resolver
* La versión de sí mismo que quería construir


Ejemplo malo: "¿Has visto el nuevo algoritmo de Meta?"
Ejemplo bueno: "Tu objetivo a 90 días era cerrar 15K/mes con ads. Vamos por el día 42. ¿En qué número reales estamos?"


Mecanismo neurológico: reconexión con valor percibido. Recuerda al cliente POR QUÉ está trabajando contigo.


R — Regalo micro


Da antes de pedir. Algo pequeño pero valioso:


* Un insight específico para su caso
* Una pregunta potente que lo haga pensar
* Un recurso mínimo (link, quote, framework de 3 líneas)
* Una observación que le ahorre tiempo


Ejemplo malo: "¿Necesitas que hagamos una llamada?"
Ejemplo bueno: "Observación: cuando tu ROAS bajó al 1.8 el mes pasado, fue justo cuando cambiaste el ángulo del creativo a 'autoridad'. El ángulo que te funciona es 'contrarian'. Vuelve a ese."


Mecanismo neurológico: principio de reciprocidad. Crea micro-deuda psicológica que el cliente quiere saldar respondiendo.


K — Kickback (respuesta fácil)


Termina con una pregunta de bajísima fricción. Debe poder responderse en una línea o con un emoji.


* Sí / no
* Escala 1-10
* Elección entre dos opciones
* Una palabra
* Un emoji


Ejemplo malo: "Cuéntame qué tal va todo y cómo te sientes con el proceso."
Ejemplo bueno: "Del 1 al 10, ¿cuánta claridad tienes ahora mismo sobre el siguiente paso?"


Mecanismo neurológico: minimiza esfuerzo cognitivo. Maximiza probabilidad de respuesta. Una vez el cliente responde una línea, es mucho más probable que la conversación siga.


Ejemplo completo aplicando S.P.A.R.K.


> P: Tengo una hipótesis sobre por qué llevas 12 días sin escribir, y quiero que me digas si acierto.
>
> S: La última vez comentaste que ibas a probar el ángulo "error que cuesta millones" en España, pero los números no llegaron a ROAS 2.
>
> A: Tu objetivo a 60 días era estabilizar CPL en 8€. Vamos por el día 34.
>
> R: Revisé la librería de ads de tu mercado ayer y detecté que 3 competidores están copiando tu estructura. Eso te da 2 semanas de ventana antes de que se sature. Hay que cambiar el framing ya — tengo una dirección específica que creo que te funciona.
>
> K: ¿Te cuento por aquí o lo vemos el lunes?


Este mensaje tiene:
* Señal específica (el ángulo concreto, los 12 días)
* Patrón interrumpido (apertura con hipótesis, no saludo)
* Anclaje al outcome (CPL 8€, día 34 de 60)
* Regalo micro (info del mercado + 2 semanas de ventana)
* Kickback con fricción cero (aquí o el lunes, 2 opciones)


Probabilidad de respuesta: altísima. Probabilidad de reactivar ejecución: muy alta.


────────────────────────────────


4. Arquitectura del sistema — visión aérea


┌─────────────────────────────────────────────────────────────┐
│                        MAC MINI / MACBOOK                     │
│                                                               │
│  ┌─────────────┐         ┌──────────────────────────────┐    │
│  │   launchd   │───2x────▶│   activation_agent.py        │    │
│  │  (9am/6pm)  │  al día │                              │    │
│  └─────────────┘          │  1. Fetch Discord channels  │    │
│                           │  2. Detect inactive clients │    │
│                           │  3. Read memory/             │    │
│                           │  4. Call Claude API          │    │
│                           │  5. Send message             │    │
│                           │  6. Update memory            │    │
│                           │  7. Log action               │    │
│                           └──────┬──────────┬────────────┘    │
│                                  │          │                 │
│                                  ▼          ▼                 │
│                    ┌─────────────────┐  ┌──────────────┐     │
│                    │  clients/       │  │  logs/       │     │
│                    │   [slug]/       │  │   YYYY-MM-DD │     │
│                    │    memory.md    │  │    .log      │     │
│                    └─────────────────┘  └──────────────┘     │
└──────────────┬──────────────────────────────────┬────────────┘
               │                                  │
               ▼                                  ▼
       ┌───────────────┐                 ┌──────────────────┐
       │  Discord API  │                 │  Anthropic API    │
       │  (bot token)  │                 │  (Claude 4.7)     │
       └───────────────┘                 └──────────────────┘


Flujo resumido


* **Scheduler local** (launchd) dispara el script 2 veces al día
* **Agente** se conecta al Discord via bot token
* **Lista canales** de clientes (definidos en `config/clients.json`)
* **Analiza actividad** de cada cliente: ¿cuándo fue el último mensaje del cliente?
* **Filtra inactivos** (> umbral definido, por defecto 7 días)
* **Para cada inactivo:**
* Lee los últimos 30 mensajes del canal
* Lee `clients/[slug]/memory.md`
* Llama a Claude API con el Mega Prompt
* Recibe mensaje S.P.A.R.K. generado
* Envía mensaje al canal via Discord API
* Actualiza `clients/[slug]/memory.md` con fecha y resumen del mensaje enviado
* Escribe entry en `logs/`


────────────────────────────────


5. Prerrequisitos técnicos


Cuentas y accesos


* **Discord:** servidor con permisos de admin
* **Anthropic:** cuenta con API key (console.anthropic.com)
* **Mac:** macOS con terminal


Software


# Python 3.11+
python3 --version  # debe devolver 3.11 o superior


# Homebrew (si no lo tienes)
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"


# pip y virtualenv
python3 -m pip install --upgrade pip
python3 -m pip install virtualenv


# Claude Code (opcional pero recomendado para mantener el agente)
npm install -g @anthropic-ai/claude-code


Dependencias Python


pip install discord.py anthropic python-dotenv pyyaml


Variables de entorno (`.env`)


DISCORD_BOT_TOKEN=tu_token_aqui
ANTHROPIC_API_KEY=sk-ant-api03-xxx
GUILD_ID=123456789012345678
INACTIVITY_THRESHOLD_DAYS=7
DRY_RUN=false


DRY_RUN=true hace que el agente genere mensajes pero NO los envíe. Úsalo las primeras 2 semanas para validar calidad antes de ir en producción.


────────────────────────────────


6. Setup paso a paso — Discord Bot


6.1 Crear la aplicación


* Ve a https://discord.com/developers/applications
* Click en **New Application**
* Nombre: `HTO Activation Agent` (o el que prefieras)
* Aceptar términos y crear


6.2 Convertir en bot


* Menú lateral → **Bot**
* Click **Add Bot** → confirmar
* Copia el **Token** (aparece una sola vez — si lo pierdes, regeneras)
* Guárdalo en `.env` como `DISCORD_BOT_TOKEN`


6.3 Activar intents


En la misma sección Bot, baja hasta Privileged Gateway Intents y activa:


* **MESSAGE CONTENT INTENT** (obligatorio para leer mensajes)
* **SERVER MEMBERS INTENT** (para listar miembros)
* **PRESENCE INTENT** (opcional, para detectar actividad extra)


Guarda cambios.


6.4 Permisos de bot


Menú lateral → OAuth2 → URL Generator


* Scopes: `bot`, `applications.commands`
* Bot permissions:
* Read Messages/View Channels
* Send Messages
* Read Message History
* Add Reactions
* Embed Links


Copia la URL generada y pégala en el navegador → selecciona tu servidor → autorizar.


6.5 Configurar el Guild ID


* Activa "modo desarrollador" en Discord (Ajustes usuario → Avanzado → Modo desarrollador)
* Click derecho en tu servidor → **Copiar ID de servidor**
* Pégalo en `.env` como `GUILD_ID`


────────────────────────────────


7. Setup paso a paso — estructura de proyecto


mkdir -p ~/discord-activation-agent
cd ~/discord-activation-agent


# Estructura de carpetas
mkdir -p clients config logs prompts scripts


# Virtualenv
python3 -m venv venv
source venv/bin/activate
pip install discord.py anthropic python-dotenv pyyaml


Árbol del proyecto


discord-activation-agent/
├── .env                          # secrets
├── .gitignore                    # ignora .env, venv, logs, clients/*/memory.md
├── README.md                     # documentación interna
├── activation_agent.py           # script principal
├── config/
│   ├── clients.json              # mapeo cliente ↔ canal discord
│   └── settings.yaml             # umbrales, horarios, límites
├── prompts/
│   ├── mega_prompt.md            # EL prompt (ver sección 9)
│   └── message_templates.md      # banco de ángulos brainstorm
├── clients/
│   ├── cliente-slug-1/
│   │   └── memory.md             # memoria viva del cliente
│   └── cliente-slug-2/
│       └── memory.md
├── scripts/
│   ├── onboard_client.py         # crea memory.md para cliente nuevo
│   └── debug_dryrun.py           # genera mensajes sin enviar, para review
├── logs/
│   └── 2026-04-20.log
└── venv/


`.gitignore`


.env
venv/
logs/
clients/*/memory.md
__pycache__/
*.pyc


`config/clients.json`


{
  "clients": [
    {
      "slug": "alejandro-perez",
      "name": "Alejandro Pérez",
      "discord_channel_id": "1234567890123456789",
      "discord_user_id": "9876543210987654321",
      "program": "DWY Coaching",
      "start_date": "2026-02-15",
      "end_date": "2026-08-15",
      "primary_goal": "Estabilizar ads a ROAS 3+ con CPL <10€",
      "active": true
    }
  ],
  "global": {
    "inactivity_threshold_days": 7,
    "max_messages_per_client_per_month": 4,
    "business_hours_only": true,
    "timezone": "Europe/Madrid"
  }
}


`config/settings.yaml`


anthropic:
  model: claude-sonnet-4-6-20250514
  max_tokens: 1024
  temperature: 0.7


discord:
  fetch_message_limit: 30
  send_delay_seconds: 3


logic:
  weekend_send: false
  skip_if_recent_admin_message_hours: 24
  respect_mute_flag: true


safety:
  require_memory_file: true
  abort_if_token_missing: true
  max_messages_per_run: 20


────────────────────────────────


8. Memoria por cliente — el cerebro del agente


Cada cliente tiene un archivo en clients/[slug]/memory.md. Este archivo es el contexto compuesto que el agente lee antes de generar cualquier mensaje. Se actualiza automáticamente después de cada reactivación.


Plantilla `memory.md`


---
slug: alejandro-perez
name: Alejandro Pérez
program: DWY Coaching
start_date: 2026-02-15
primary_goal: Estabilizar ads a ROAS 3+ con CPL <10€
status: active
---


## Tesis del cliente


Agencia de marketing B2B en España. Quiere escalar de 20K/mes a 50K/mes vendiendo a coaches de fitness. Su mecanismo: ángulos contrarians en ads + VSL de 8 min + cierre one-call.


## Historial de interacciones


### 2026-02-15 — Onboarding call
- Definió goal a 90 días: 15K/mes nuevos de ads
- Bloqueo inicial: no sabía estructurar ángulos
- Le di framework "Villano/Héroe/Arma"


### 2026-03-02 — Primera crisis
- ROAS bajó a 1.4
- Diagnóstico: cambió el ángulo a "autoridad" sin testear
- Recomendación: volver a "contrarian"


### 2026-03-18 — Win
- ROAS recuperado a 2.6
- CPL en 11€ (cerca del target 8€)
- Momentum alto


### 2026-04-08 — Último mensaje
- Dijo que iba a lanzar el ángulo "error que cuesta millones"
- No ha escrito desde entonces (12 días)


## Ángulos que le resuenan


- Contrarian
- Error/dolor específico
- Data concreta
- Case studies


## Ángulos que NO le resuenan


- Autoridad
- Aspiracional genérico
- Emocional suave


## Contexto personal relevante


- Dos hijos pequeños, suele desconectar los sábados
- Zona horaria: España
- Prefiere mensajes directos sin floritura
- Responde mejor a preguntas cerradas


## Mensajes de reactivación previos


### 2026-03-26
- Enviado: "Tu CPL lleva 3 días por encima de 12€. Hipótesis: el nuevo creativo no tiene gancho en los primeros 3 segundos. ¿Te paso el nuevo template de hooks o lo vemos el lunes?"
- Respuesta: "Pásamelo, lunes hay llamada con equipo"
- Resultado: reactivación exitosa, ejecutó el mismo día


## Notas estratégicas activas


- Fase del programa: mes 2 de 6
- Siguiente milestone: estabilizar CPL en 8€
- Riesgo de churn: BAJO (cliente en momentum)
- Próximo paso natural: abrir 2º mercado (México)


Cómo se actualiza


Al final de cada ejecución del agente, el script añade una entrada nueva bajo ## Mensajes de reactivación previos con:


* Fecha y hora
* Mensaje enviado
* (Cuando el cliente responda, se añade manualmente o via un segundo hook)


Además, si el mensaje generado detecta un cambio de contexto relevante (nuevo goal, nuevo bloqueo, nuevo win reportado en los últimos mensajes del canal), el agente añade una entrada en ## Historial de interacciones.


────────────────────────────────


9. El Mega Prompt — corazón del sistema


Este es el prompt que recibe Claude en cada llamada. Se guarda en prompts/mega_prompt.md y se carga como system prompt cada ejecución.


El prompt completo


# ROL


Eres el agente de activación de clientes de [NOMBRE_DEL_OPERADOR], consultor high-ticket especializado en [NICHO]. Tu único trabajo es escribir mensajes de reactivación en Discord para clientes activos del programa que llevan días sin actividad.


No eres un chatbot. No respondes mensajes. No conversas. Solo generas UN mensaje por cliente por ejecución, cuando se te pide explícitamente.


# CONTEXTO DEL NEGOCIO


[NOMBRE_DEL_OPERADOR] dirige [NEGOCIO], un programa de consulting/coaching high-ticket. Los clientes pagan entre [X]€ y [Y]€ por un ciclo de [Z] meses. La relación es cercana, directa, sin florituras corporativas. El tono es de operador a operador: sharp, respetuoso, zero fluff.


El objetivo estratégico de este agente: mantener a cada cliente enganchado, en ejecución y con dopamina alta a lo largo de todo su ciclo, detectando silencios peligrosos antes de que se conviertan en churn.


# INPUTS QUE RECIBIRÁS EN CADA EJECUCIÓN


1. **Nombre y contexto del cliente** (desde `clients/[slug]/memory.md`)
2. **Últimos 30 mensajes del canal privado del cliente** (desde Discord API)
3. **Número de días sin actividad del cliente**
4. **Fecha del último mensaje del operador al cliente**
5. **Historial de mensajes de reactivación previos y sus respuestas**


# TU TAREA


Generar UN mensaje de reactivación que aplique rigurosamente el framework S.P.A.R.K.:


**S — Señal:** Referencia algo específico, concreto y reciente del cliente. Algo sacado de los últimos 30 mensajes, del memory.md o del historial. Demuestra que prestas atención.


**P — Patrón interrumpido:** NO abras con saludo típico ("hola", "qué tal", "espero que bien"). Abre con algo inesperado: una hipótesis, una observación filosa, una declaración contra-intuitiva, una pregunta punzante.


**A — Anclaje al outcome:** Conecta el mensaje con el objetivo original que el cliente definió en onboarding o con el milestone actual de su programa. Usa números o deadlines específicos cuando los tengas.


**R — Regalo micro:** Incluye algo de valor: un insight específico para su caso, una observación sobre su mercado, un framework corto, una pregunta potente que lo haga pensar. NO promociones servicios extra. NO vendas. Da.


**K — Kickback:** Termina con una pregunta de BAJÍSIMA fricción. Debe poder responderse en una línea, con un emoji o eligiendo entre dos opciones. NUNCA preguntes "¿cómo vas?" o "¿qué tal todo?" — son fricción alta disfrazada de casualidad.


# REGLAS DURAS


1. **Máximo 6 líneas** de mensaje. Debe leerse en <15 segundos.
2. **Español siempre.** Nunca inglés mezclado (excepto términos técnicos consolidados como "ROAS", "CPL", "high ticket").
3. **Voz de operador**, nunca corporativa. Directo, afilado, sin emojis salvo si el cliente usa emojis en su estilo habitual.
4. **Cero relleno.** Nada de "espero que estés bien", "sé que estás ocupado", "no quiero molestar".
5. **No menciones que eres un agente o bot.** El mensaje debe leerse como escrito por [NOMBRE_DEL_OPERADOR].
6. **No inventes datos.** Si no tienes un número específico, no lo uses. Prefiere precisión vaga a falsedad precisa.
7. **Evita estas palabras:** "simplemente", "realmente", "genial", "absolutamente", "increíble".
8. **No uses emojis** a menos que la memoria del cliente indique que él los usa.
9. **Si el cliente ya recibió un mensaje de reactivación en los últimos 10 días sin respuesta**, cambia completamente el ángulo. Nunca insistir con el mismo approach.
10. **Si el cliente parece estar en silencio por vida** (señales de crisis personal detectadas en mensajes recientes, baja prolongada inexplicable), el mensaje debe ser humano, corto, sin pedir nada. Ejemplo: "Te he visto callado. Ningún pitch, solo check-in. Cuando vuelvas, estoy aquí."


# PROCESO MENTAL (sigue este orden interno)


1. Lee la memoria del cliente completa
2. Lee los últimos 30 mensajes del canal
3. Identifica: ¿qué tipo de silencio es este? (ejecución / bloqueo / duda / vida)
4. Elige el ángulo apropiado del brainstorming (sección 12)
5. Genera borrador aplicando S.P.A.R.K.
6. Revisa contra las 10 reglas duras
7. Acorta si pasas de 6 líneas
8. Entrega


# FORMATO DE OUTPUT


Responde ÚNICAMENTE con un JSON válido con esta estructura:


{
  "silence_type": "execution | block | doubt | life",
  "angle_used": "check-in | win-recall | pattern-interrupt | micro-insight | question-bomb | accountability-nudge | silent-rupture-repair",
  "message": "El mensaje literal que se enviará al cliente",
  "reasoning": "2-3 líneas explicando por qué elegiste este ángulo y qué señal específica usaste",
  "memory_update": "Entrada breve para añadir al historial del cliente — qué se envió y por qué"
}


No incluyas nada fuera del JSON. Ni explicaciones previas, ni despedidas.


# EJEMPLOS FEW-SHOT


## Ejemplo 1 — Silencio por ejecución


**Input resumido:**
Cliente: Alejandro Pérez. Días sin escribir: 12. Memoria indica: lanzó ángulo "error que cuesta millones" hace 2 semanas, ROAS iba a 2.6 en último reporte, está en mes 2 de 6, goal de CPL 8€.


**Output:**
{
  "silence_type": "execution",
  "angle_used": "accountability-nudge",
  "message": "Hipótesis sobre por qué llevas 12 días en silencio: estás ejecutando, no atascado. Dime si acierto.\n\nEl ángulo 'error que cuesta millones' lo lanzaste hace 2 semanas. Tu CPL objetivo era 8€. Vamos por el día 34 de 90.\n\nDato rápido: vi 3 competidores copiando tu estructura en librería de ads ayer. Tienes 2 semanas de ventana antes de que se sature.\n\n¿Te paso la siguiente iteración del ángulo o lo vemos en la call del lunes?",
  "reasoning": "Cliente en momentum ejecutando. Uso accountability-nudge para validar que va bien sin interrumpir flow. Regalo: info competitiva accionable. Kickback binario: aquí o llamada.",
  "memory_update": "2026-04-20: Enviado accountability-nudge sobre saturación competencia ángulo contrarian. Pregunta binaria aquí/lunes."
}


## Ejemplo 2 — Silencio por bloqueo


**Input resumido:**
Cliente: María López. Días sin escribir: 9. Últimos mensajes muestran frustración con un funnel que no convierte. Memoria: está en mes 3 de 6, ha tenido bajón de confianza.


**Output:**
{
  "silence_type": "block",
  "angle_used": "micro-insight",
  "message": "Nota: cuando un funnel deja de convertir, el problema rara vez es el funnel.\n\nEn tus últimos mensajes mencionaste que el checkout abandonaba al 70%. Eso no es un problema de funnel, es un problema de fit oferta-audiencia en el paso previo.\n\nHe pensado en tu caso específico y veo una palanca que no estamos tocando: el ángulo de tu VSL asume que el cliente ya sabe que tiene el problema. Y no lo sabe.\n\n¿Te lo explico por aquí en 3 líneas más, o abrimos hueco esta semana?",
  "reasoning": "Cliente bloqueada con frustración detectable. Uso micro-insight como regalo + reframe del problema (no es el funnel, es el paso previo). Kickback binario baja fricción sin forzar call.",
  "memory_update": "2026-04-20: Enviado micro-insight sobre VSL asumiendo awareness del problema. Reframe del bloqueo del funnel."
}


## Ejemplo 3 — Silencio por vida


**Input resumido:**
Cliente: Rafa Jiménez. Días sin escribir: 21. Último mensaje: "Esta semana imposible, tema familiar complicado". Memoria: mes 4 de 6, hasta ahora cliente excelente.


**Output:**
{
  "silence_type": "life",
  "angle_used": "silent-rupture-repair",
  "message": "Te he visto callado 3 semanas. No vengo con pitch.\n\nSolo un check-in humano: ¿estás bien?\n\nCuando vuelvas, el programa sigue aquí igual que lo dejaste. Sin presión.",
  "reasoning": "Cliente había mencionado tema familiar. Uso silent-rupture-repair: cero pitch, cero pregunta sobre negocio, solo humanidad. La relación se fortalece aquí más que vendiéndole algo.",
  "memory_update": "2026-04-20: Enviado check-in humano sin pitch. Respetando silencio por vida tras mención de tema familiar hace 3 semanas."
}


# AHORA GENERA EL MENSAJE


A continuación recibirás el contexto específico del cliente de esta ejecución. Aplica el framework, respeta las reglas, devuelve el JSON.


────────────────────────────────


10. Detección de inactividad — lógica de scoring


No todos los "días sin escribir" son iguales. El agente usa un scoring ligero para priorizar.


Señales de inactividad


| Señal | Peso |
|-------|------|
| Días sin mensaje del cliente | +1 por día |
| Días sin reacción a mensaje del operador | +0.3 por día |
| Mensaje del operador sin respuesta >3 días | +2 |
| Cliente escribió "voy a hacer X" y no reportó | +3 después de 5 días |
| Cliente mencionó bloqueo sin resolver | +4 |
| Hay call programada sin confirmar | +2 |


Trigger


Si score >= 7, el cliente entra en la lista de reactivación de esa ejecución.


Esto evita:
* Escribir a clientes que están en flow sano
* Escribir a clientes que acabas de escribir tú
* Multiplicar mensajes en periodos cortos


Excepciones (nunca reactivar)


* Cliente con flag `muted: true` en clients.json
* Cliente en periodo de onboarding (<7 días desde start_date)
* Cliente en últimos 14 días del programa (ahí manda CSM humano)
* Cliente que ya recibió 4+ mensajes de reactivación en los últimos 30 días


────────────────────────────────


11. Scheduling con launchd en Mac


Archivo plist


Crea ~/Library/LaunchAgents/com.hto.activation-agent.plist:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
        "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.hto.activation-agent</string>


    <key>ProgramArguments</key>
    <array>
        <string>/Users/TU_USUARIO/discord-activation-agent/venv/bin/python</string>
        <string>/Users/TU_USUARIO/discord-activation-agent/activation_agent.py</string>
    </array>


    <key>WorkingDirectory</key>
    <string>/Users/TU_USUARIO/discord-activation-agent</string>


    <key>StartCalendarInterval</key>
    <array>
        <dict>
            <key>Hour</key>
            <integer>9</integer>
            <key>Minute</key>
            <integer>0</integer>
        </dict>
        <dict>
            <key>Hour</key>
            <integer>18</integer>
            <key>Minute</key>
            <integer>0</integer>
        </dict>
    </array>


    <key>StandardOutPath</key>
    <string>/Users/TU_USUARIO/discord-activation-agent/logs/stdout.log</string>


    <key>StandardErrorPath</key>
    <string>/Users/TU_USUARIO/discord-activation-agent/logs/stderr.log</string>


    <key>RunAtLoad</key>
    <false/>


    <key>EnvironmentVariables</key>
    <dict>
        <key>PATH</key>
        <string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
    </dict>
</dict>
</plist>


Cargar el job


launchctl load ~/Library/LaunchAgents/com.hto.activation-agent.plist
launchctl start com.hto.activation-agent


Verificar que está cargado


launchctl list | grep hto.activation


Cambiar horarios


Edita el plist, descarga y recarga:


launchctl unload ~/Library/LaunchAgents/com.hto.activation-agent.plist
# edita el archivo
launchctl load ~/Library/LaunchAgents/com.hto.activation-agent.plist


Monitoreo


Los logs se guardan en logs/YYYY-MM-DD.log. Revisa diariamente la primera semana. Después, chequeo semanal basta.


────────────────────────────────


12. Brainstorming de tipos de mensaje


El agente elige entre estos 7 ángulos base según el contexto. Cada uno cumple S.P.A.R.K. pero activa matices distintos.


1. Check-in con hipótesis
Abre con una hipótesis sobre por qué el cliente está callado. Formato: "Tengo una teoría sobre por qué X, y quiero que me digas si acierto."


Cuándo usar: Silencio por ejecución o silencio ambiguo.


2. Win-recall
Recuerda un win reciente del cliente y construye desde ahí. "Hace X días hiciste Y. Ahora toca Z."


Cuándo usar: Cliente con momentum reciente pero sin reportar en días.


3. Pattern-interrupt filoso
Abre con una declaración contra-intuitiva. "La mayoría de consultores te dirían X. Mi experiencia dice Y."


Cuándo usar: Cliente con signos de duda, consumiendo contenido de otros.


4. Micro-insight gift
Da un insight específico no solicitado sobre su caso. Puro regalo.


Cuándo usar: Cliente en bloqueo. El regalo baja la fricción emocional.


5. Question-bomb
Todo el mensaje gira alrededor de UNA pregunta potente que lo hace pensar diferente.


Cuándo usar: Cliente estancado estratégicamente. Necesita reframe.


6. Accountability nudge
Referencia un compromiso específico que hizo. "Dijiste X, ¿dónde estamos?"


Cuándo usar: Cliente con compromiso reciente no reportado. Funciona bien con perfiles high-performer.


7. Silent-rupture repair
Cero pitch. Cero negocio. Solo humanidad.


Cuándo usar: Silencio por vida. Crisis personal detectada.


Cómo elige el agente


El Mega Prompt instruye al modelo a:


* Clasificar el tipo de silencio (execution / block / doubt / life)
* Elegir el ángulo apropiado según esa clasificación
* Nunca repetir el mismo ángulo dos ejecuciones seguidas al mismo cliente


────────────────────────────────


13. Reglas duras y anti-patterns


Reglas que nunca se rompen


* **Horario humano.** Nunca enviar entre 22:00 y 9:00 del timezone del cliente.
* **Fin de semana off.** Por defecto no enviar sábado ni domingo (configurable).
* **Máximo 4 mensajes de reactivación por cliente al mes.** Más de eso es hostigamiento.
* **No enviar si el operador escribió en las últimas 24h.** Respeta el touch humano.
* **No enviar si el cliente escribió en las últimas 24h.** Si ya hay conversación, el agente no interrumpe.
* **No sugerir cancelar, pausar ni reducir programa.** Eso es conversación humana, nunca automatizada.
* **No pedir upsells, no promocionar backend.** Este agente sirve retención, no cross-sell.


Anti-patterns (nunca generar)


* "¿Cómo vas?"
* "Espero que estés bien"
* "Sé que estás ocupado, pero..."
* "Solo quería saludar"
* "Pasando a saludar"
* "Recordatorio amistoso"
* Cualquier frase con "simplemente", "realmente", "absolutamente"
* Emojis sin contexto (🔥💪🚀)
* CTAs agresivos ("agenda ya", "no lo dejes pasar")
* Mensajes de más de 6 líneas
* Preguntas de alta fricción ("cuéntame cómo te sientes con el proceso")
Safeguards técnicos


En activation_agent.py:


def should_skip_client(client, last_admin_message_hours, recent_reactivations):
    if client.muted:
        return True
    if last_admin_message_hours < 24:
        return True
    if len(recent_reactivations) >= 4:
        return True
    if is_weekend() and not settings.weekend_send:
        return True
    if not within_business_hours(client.timezone):
        return True
    return False


────────────────────────────────
14. Métricas y evolución del agente
KPIs a trackear


Crea un archivo logs/metrics.csv que se actualiza cada ejecución:


| Fecha | Clientes escaneados | Inactivos detectados | Mensajes enviados | Respuestas en 48h | Tasa respuesta |
|-------|---------------------|----------------------|-------------------|-------------------|----------------|
Benchmarks iniciales


* Primera semana:
* Tasa de respuesta objetivo: >40%
* Si <25%: el prompt no está calibrado, revisar ángulos
* Si >70%: el agente está funcionando bien, escalar frecuencia
Evolución del prompt


* Cada mes, revisa:
* * Los 5 mensajes que más respuesta generaron → ¿qué ángulo usaban?
* Los 5 mensajes sin respuesta → ¿qué patrón tenían?
* Ajusta los few-shot examples del Mega Prompt con los mejores casos reales
Fase 2 — el agente aprende


* Cuando tengas 50+ mensajes enviados con respuestas, puedes:
* * Crear un dataset de `(context, message, response, outcome)`
* Usarlo para prompt-tuning (añadir más few-shot reales)
* Futuro: fine-tune un modelo propio si el volumen lo justifica


────────────────────────────────
15. Troubleshooting
* El bot no recibe mensajes
* * Verifica que **Message Content Intent** está activado en Developer Portal
* Verifica que el bot tiene permiso `Read Message History` en el canal
* El bot envía pero el mensaje sale raro
* * Probablemente estás enviando JSON literal. Verifica que extraes `message` del output antes de llamar a `channel.send()`
* launchd no ejecuta a la hora
* * Verifica que el Mac no estaba suspendido. Si duermes el Mac, usa Mac Mini siempre encendido o mueve el scheduling a GitHub Actions
* Revisa `logs/stderr.log`
* Claude devuelve JSON mal formado
* * Baja la `temperature` a 0.5
* Añade `response_format="json_object"` si estás usando el SDK oficial
* Añade validación en Python con `try/except json.JSONDecodeError` y retry una vez
* El mensaje generado es genérico
* * Revisa que estás pasando bien `memory.md` + últimos 30 mensajes al prompt
* Añade en el system prompt: "Si no tienes contexto específico suficiente para aplicar S.P.A.R.K., NO envíes mensaje. Devuelve `skip: true`."
* El bot spammea
* * Verifica los safeguards de la sección 13
* Reduce `max_messages_per_run` en settings.yaml a 5 las primeras 2 semanas


────────────────────────────────
16. Anexo A — código completo
`activation_agent.py`


"""
HTO Activation Agent
Ejecuta 2x al día, detecta clientes inactivos, genera mensaje S.P.A.R.K., envía.
"""


import os
import json
import yaml
import discord
import asyncio
from datetime import datetime, timedelta, timezone
from pathlib import Path
from dotenv import load_dotenv
from anthropic import Anthropic


load_dotenv()


ROOT = Path(__file__).parent
CONFIG = json.loads((ROOT / "config" / "clients.json").read_text())
SETTINGS = yaml.safe_load((ROOT / "config" / "settings.yaml").read_text())
MEGA_PROMPT = (ROOT / "prompts" / "mega_prompt.md").read_text()


anthropic = Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
intents = discord.Intents.default()
intents.message_content = True
intents.members = True
client = discord.Client(intents=intents)


DRY_RUN = os.environ.get("DRY_RUN", "false").lower() == "true"


async def fetch_last_messages(channel, limit=30):
    messages = []
    async for msg in channel.history(limit=limit):
        messages.append({
            "author_id": str(msg.author.id),
            "author_name": msg.author.display_name,
            "timestamp": msg.created_at.isoformat(),
            "content": msg.content,
        })
    return list(reversed(messages))


def compute_inactivity_score(client_config, messages):
    now = datetime.now(timezone.utc)
    client_user_id = client_config["discord_user_id"]


    last_client_msg = None
    last_admin_msg = None
    for msg in reversed(messages):
        ts = datetime.fromisoformat(msg["timestamp"])
        if msg["author_id"] == client_user_id and last_client_msg is None:
            last_client_msg = ts
        if msg["author_id"] != client_user_id and last_admin_msg is None:
            last_admin_msg = ts
        if last_client_msg and last_admin_msg:
            break


    score = 0
    if last_client_msg:
        days_silent = (now - last_client_msg).days
        score += days_silent
    else:
        score += 14


    if last_admin_msg and last_client_msg and last_admin_msg > last_client_msg:
        hours_since_admin = (now - last_admin_msg).total_seconds() / 3600
        if hours_since_admin > 72:
            score += 2


    return score, last_admin_msg, last_client_msg


def load_memory(slug):
    path = ROOT / "clients" / slug / "memory.md"
    if not path.exists():
        return None
    return path.read_text()


def update_memory(slug, memory_update):
    path = ROOT / "clients" / slug / "memory.md"
    content = path.read_text()
    today = datetime.now().strftime("%Y-%m-%d")
    entry = f"\n\n### {today} — Reactivación automática\n{memory_update}\n"
    content = content.rstrip() + entry
    path.write_text(content)


def call_claude(memory, messages, client_config, days_silent):
    user_message = f"""
CLIENTE: {client_config['name']} (slug: {client_config['slug']})
DÍAS SIN ACTIVIDAD: {days_silent}
PROGRAMA: {client_config['program']}
GOAL PRIMARIO: {client_config['primary_goal']}


---
MEMORIA DEL CLIENTE:


{memory}


---
ÚLTIMOS 30 MENSAJES DEL CANAL:


{json.dumps(messages, indent=2, ensure_ascii=False)}
"""


    response = anthropic.messages.create(
        model=SETTINGS["anthropic"]["model"],
        max_tokens=SETTINGS["anthropic"]["max_tokens"],
        temperature=SETTINGS["anthropic"]["temperature"],
        system=MEGA_PROMPT,
        messages=[{"role": "user", "content": user_message}],
    )


    raw = response.content[0].text
    try:
        return json.loads(raw)
    except json.JSONDecodeError:
        start = raw.find("{")
        end = raw.rfind("}") + 1
        return json.loads(raw[start:end])


def should_skip(client_config, last_admin_msg):
    now = datetime.now(timezone.utc)
    if last_admin_msg:
        hours_since = (now - last_admin_msg).total_seconds() / 3600
        if hours_since < SETTINGS["logic"]["skip_if_recent_admin_message_hours"]:
            return True
    return False


@client.event
async def on_ready():
    print(f"Bot connected as {client.user}")
    guild = client.get_guild(int(os.environ["GUILD_ID"]))
    log_path = ROOT / "logs" / f"{datetime.now().strftime('%Y-%m-%d')}.log"


    with log_path.open("a") as log:
        log.write(f"\n=== Run {datetime.now().isoformat()} ===\n")


        sent_count = 0
        for client_config in CONFIG["clients"]:
            if not client_config.get("active", True):
                continue


            channel = guild.get_channel(int(client_config["discord_channel_id"]))
            if not channel:
                log.write(f"[WARN] Channel not found for {client_config['slug']}\n")
                continue


            messages = await fetch_last_messages(channel)
            score, last_admin, last_client_msg = compute_inactivity_score(client_config, messages)


            if score < 7:
                log.write(f"[SKIP] {client_config['slug']} — score {score} <7\n")
                continue


            if should_skip(client_config, last_admin):
                log.write(f"[SKIP] {client_config['slug']} — admin wrote recently\n")
                continue


            memory = load_memory(client_config["slug"])
            if not memory:
                log.write(f"[SKIP] {client_config['slug']} — no memory file\n")
                continue


            days_silent = (datetime.now(timezone.utc) - last_client_msg).days if last_client_msg else 14


            try:
                result = call_claude(memory, messages, client_config, days_silent)
            except Exception as e:
                log.write(f"[ERROR] {client_config['slug']} — Claude call failed: {e}\n")
                continue


            message = result.get("message")
            if not message:
                log.write(f"[SKIP] {client_config['slug']} — no message generated\n")
                continue


            if DRY_RUN:
                log.write(f"[DRYRUN] {client_config['slug']} — {message[:100]}...\n")
            else:
                await channel.send(message)
                log.write(f"[SENT] {client_config['slug']} — angle: {result.get('angle_used')}\n")
                update_memory(client_config["slug"], result.get("memory_update", ""))
                sent_count += 1
                await asyncio.sleep(SETTINGS["discord"]["send_delay_seconds"])


            if sent_count >= SETTINGS["safety"]["max_messages_per_run"]:
                log.write(f"[STOP] Reached max_messages_per_run\n")
                break


        log.write(f"=== End run. Sent: {sent_count} ===\n")


    await client.close()


if __name__ == "__main__":
    client.run(os.environ["DISCORD_BOT_TOKEN"])


────────────────────────────────
17. Anexo B — launchd plist listo para usar


Ver sección 11. Reemplaza TU_USUARIO por tu nombre de usuario real (whoami te lo dice).


────────────────────────────────
18. Anexo C — checklist pre-producción


* Antes de ir en producción, confirma:
* * [ ] `.env` tiene `DISCORD_BOT_TOKEN`, `ANTHROPIC_API_KEY`, `GUILD_ID`
* [ ] `.env` tiene `DRY_RUN=true` para primera ejecución
* [ ] `config/clients.json` tiene al menos 1 cliente configurado
* [ ] Cada cliente activo tiene su `clients/[slug]/memory.md` creado
* [ ] Bot invitado al servidor con permisos correctos
* [ ] `activation_agent.py` corre sin errores ejecutándolo manualmente
* [ ] Logs se escriben en `logs/`
* [ ] launchd plist cargado con `launchctl load`
* [ ] Primera semana en `DRY_RUN=true` revisando mensajes generados
* [ ] Segunda semana: activar uno o dos clientes de confianza en producción
* [ ] Tercera semana: rollout completo
Test rápido


cd ~/discord-activation-agent
source venv/bin/activate
DRY_RUN=true python activation_agent.py


Revisa logs/YYYY-MM-DD.log para ver qué habría hecho. Si los mensajes generados son de calidad premium, desactiva DRY_RUN y ya está funcionando en vivo.


────────────────────────────────
Cierre


Este sistema no sustituye la relación humana con tu cliente. La amplifica.


Lo que hace es asegurar que ningún cliente se apague en silencio. Cada día hay dos momentos donde un operador proactivo habría escrito. Ahora esos dos momentos ocurren sin falta, con calidad premium, personalizados al contexto específico de cada cliente.


El retorno no está en los mensajes enviados. Está en los churns evitados.


Un cliente high-ticket salvado paga el sistema 100 veces.


────────────────────────────────


© 2026 HTO Consulting LLC · highticketoperator.io · Todos los derechos reservados


© 2026 HTO Consulting LLC · highticketoperator.io · Todos los derechos reservados