feat: centralizar ADN y estilo de marca en Zona de Marca y simplificar panel de creatividades a operativa ads

This commit is contained in:
Marcos Fargas
2026-10-14 14:51:41 +02:00
parent feef339c2b
commit aed73e0171
6 changed files with 2483 additions and 1425 deletions

View File

@@ -70,6 +70,27 @@ export type BrandPersona = {
channels?: string;
};
export type CreativeB2BPersona = {
name: string;
role: string;
goal: string;
pain_point: string;
buying_trigger: string;
};
export type CreativeB2CPersona = {
name: string;
age_range: string;
situation: string;
pain_point: string;
goal: string;
};
export type CreativeObjection = {
objection: string;
rebuttal: string;
};
export type Brandbook = {
version: 2;
updated_at?: string;
@@ -85,6 +106,11 @@ export type Brandbook = {
vision: string;
values: string[];
vertical: string;
b2b_offering?: string;
b2c_offering?: string;
usp_differentiators?: string;
current_priorities?: string;
website?: string;
/** Voz */
tone: string;
personality: string[];
@@ -96,6 +122,10 @@ export type Brandbook = {
example_good: string[];
example_bad: string[];
slogan_variants: string[];
copy_structure_rules?: string;
english_terms_policy?: string;
power_words?: string[];
allowed_ctas?: string[];
/** Visual kit */
colors: ColorToken[];
fonts_kit: FontToken[];
@@ -112,10 +142,18 @@ export type Brandbook = {
icon_style: string;
imagery_do: string[];
imagery_dont: string[];
surface_rules?: string;
image_text_rules?: string;
typography_headline?: string;
typography_body?: string;
kit_assets: BrandAsset[];
/** Audiencia */
icp: string;
personas: BrandPersona[];
b2b_personas?: CreativeB2BPersona[];
b2c_personas?: CreativeB2CPersona[];
customer_frustrations?: string;
desired_transformation?: string;
geo: string;
languages: string[];
jobs_to_be_done: string[];
@@ -131,6 +169,7 @@ export type Brandbook = {
competitive_alternatives: string[];
guarantee: string;
pricing_notes: string;
price_and_offer_framing?: string;
/** Pilares */
pillars_rrss: string[];
series_recurrentes: string[];
@@ -169,6 +208,9 @@ export type Brandbook = {
ads_formats: string[];
landing_notes: string;
retargeting_angles: string[];
objections?: CreativeObjection[];
winning_hooks?: string[];
preferred_angles?: string[];
/** B2B */
linkedin_themes: string[];
linkedin_formats: string[];
@@ -189,6 +231,9 @@ export type Brandbook = {
legal_notes: string;
sector_rules: string;
image_rights: string;
emoji_policy?: string;
testimonials_policy?: string;
competitors_never_mention?: string[];
/** Fuentes */
source_excerpts: Array<{
id: string;
@@ -404,6 +449,30 @@ export function emptyBrandbook(label = ''): Brandbook {
image_rights: '',
source_excerpts: [],
freeform_notes: '',
b2b_offering: '',
b2c_offering: '',
usp_differentiators: '',
current_priorities: '',
website: '',
copy_structure_rules: '',
english_terms_policy: '',
power_words: [],
allowed_ctas: [],
surface_rules: '',
image_text_rules: '',
typography_headline: '',
typography_body: '',
b2b_personas: [],
b2c_personas: [],
customer_frustrations: '',
desired_transformation: '',
price_and_offer_framing: '',
objections: [],
winning_hooks: [],
preferred_angles: [],
emoji_policy: '',
testimonials_policy: '',
competitors_never_mention: [],
};
}
@@ -582,6 +651,80 @@ export function seedBrandbook(slug: string, label: string): Brandbook {
],
value_proposition:
'Aprendes inglés usable desde la primera semana, con camino claro a certificado.',
b2b_offering:
'Programas de inglés profesional para equipos. Diferencia frente a apps conversacionales tipo Twenix: profesor fijo, sesiones de 60 minutos, programas por sector (Inglés Legal, IT, Financiero, Comercial Internacional), modalidad híbrida. Es Entidad Organizadora de FUNDAE (gestiona la bonificación directamente, no a través de un partner).',
b2c_offering:
'Cursos de inglés para adultos, preparación de exámenes oficiales (Cambridge, Aptis, IELTS), intensivos, clases 1to1 y plataforma online.',
usp_differentiators:
'Profesor fijo asignado + sesiones completas de 60 min (progreso estructural frente a conversación suelta de 15-20 min en apps). Gestión integral FUNDAE como Entidad Organizadora oficial. Especialización real por vertical profesional.',
current_priorities:
'B2C: captación de matrículas de adultos y preparación de certificados oficiales (Cambridge / Aptis). B2B: captación de empresas que quieren aprovechar su crédito anual FUNDAE con programas in-company u online estructurados.',
price_and_offer_framing:
'En B2B destacar el coste bonificado vía FUNDAE (coste cero o casi cero para la empresa, expresado en coste neto o gestión bonificada). En B2C destacar el valor del programa estructurado con profesor fijo, sin recurrir nunca a descuentos agresivos ni cuentas atrás.',
website: 'blueparrotschool.com',
copy_structure_rules:
'Frases cortas. Un dato concreto vale más que un adjetivo. En primary_text: 2-4 frases, máx. 300 caracteres; la frase 1 amplía el hook, frases 2-3 dan un beneficio o dato concreto, frase final cierra con el CTA sin enlaces ni hashtags.',
english_terms_policy:
'Inglés en el copy: solo nombres propios de exámenes (Cambridge, Aptis, IELTS) o términos sin traducción natural (in-company). Nada de "boost", "unlock", "level up".',
power_words: [
'Profesor fijo',
'60 minutos',
'Entidad Organizadora FUNDAE',
'Inglés de tu sector',
'Programa estructurado',
'Madrid y Málaga + Online',
],
allowed_ctas: [
'Más información',
'Solicitar presupuesto',
'Reservar',
'Contactar',
'Registrarte',
],
b2b_personas: [
{
name: 'Marta (RRHH / Formación)',
role: 'Responsable de RRHH o Desarrollo de Talento',
goal: 'Quiere un proveedor llave en mano que no le dé trabajo administrativo.',
pain_point:
'Proveedores que obligan a RRHH a gestionar papeleo FUNDAE, perseguir asistencias o lidiar con quejas por cambios constantes de profesor.',
buying_trigger:
'Gestión FUNDAE 100% resuelta por Blue Parrot, informes periódicos de asistencia y progreso, y un único interlocutor.',
},
{
name: 'Carlos (Socio / Director de Despacho o Consultora)',
role: 'Director General o Socio en firma legal, financiera o consultoría',
goal: 'Que su equipo hable con clientes internacionales con solvencia y sin hacer el ridículo.',
pain_point:
'Su equipo sabe gramática pero se bloquea al negociar, redactar contratos o defender propuestas en inglés técnico.',
buying_trigger:
'Compra rigor y especialización sectorial (Inglés Legal, Financiero, IT), no "clases de inglés general".',
},
{
name: 'Lucía (Directora Comercial / Internacional)',
role: 'Directora Comercial o de Expansión Internacional',
goal: 'Resultados medibles en poco tiempo para presentaciones, negociación y llamadas internacionales.',
pain_point:
'Las apps conversacionales entretienen pero el equipo sigue sin cerrar reuniones complejas en inglés.',
buying_trigger:
'Sesiones de 60 minutos enfocadas en casos reales de su puesto de trabajo.',
},
],
b2c_personas: [
{
name: 'Adulto Profesional / Opositor (25-45 años)',
age_range: '25-45 años',
situation:
'Trabaja o busca salto laboral / necesita certificar nivel oficial (Cambridge B2/C1, Aptis, IELTS).',
pain_point:
'Ha fracasado o se ha estancado con apps y cursos grabados; siente que pierde el tiempo sin un profesor que le corrija y le marque ritmo.',
goal: 'Avanzar de nivel de forma real o aprobar su examen oficial con horario compatible con su jornada.',
},
],
customer_frustrations:
'Estancamiento en el "nivel intermedio eterno" por usar apps de 15 minutos con profesores distintos cada día que solo dan charla superficial sin estructura ni seguimiento.',
desired_transformation:
'Pasar de evitar hablar en inglés o depender de traductores a desenvolverse con seguridad en reuniones de trabajo o aprobar su certificación oficial a la primera.',
ads_usps: [
'Metodología práctica',
'Profes preparados',
@@ -593,6 +736,58 @@ export function seedBrandbook(slug: string, label: string): Brandbook {
'Ya probé y no aprendí',
'Es caro',
],
objections: [
{
objection: 'Ya usamos una app',
rebuttal:
'La app mantiene, no avanza. Sin profesor fijo ni sesiones de 60 min no se sale del nivel intermedio.',
},
{
objection: 'Es caro',
rebuttal:
'Bonificación FUNDAE gestionada directamente por nosotros; coste mínimo por empleado y mes.',
},
{
objection: 'No tenemos tiempo',
rebuttal:
'Sesiones in-company u online integradas dentro del horario laboral.',
},
],
winning_hooks: [
'Tu equipo no necesita más inglés. Necesita el inglés de su trabajo.',
'FUNDAE lo paga. Nosotros lo gestionamos.',
'Un profesor. Sesenta minutos. Cada semana. Así se avanza.',
'El inglés legal no se aprende en una app.',
],
preferred_angles: [
'objeción tiempo',
'coste FUNDAE',
'contraste app vs programa real',
'especialización por sector (Legal / IT / Finanzas)',
'cero carga para RRHH',
'certificación oficial (Cambridge / Aptis)',
],
claims_forbidden: [
'Prometer niveles o plazos cerrados ("B2 en 3 meses").',
'Comparar nombrando explícitamente a la competencia (ni Twenix ni ninguna).',
'Descuentos agresivos, cuentas atrás, "última oportunidad".',
'Fotografía de stock genérica de "gente feliz con portátil" con aspecto de banco de imágenes.',
'Deformar el logo, recolorearlo o recrearlo (se coloca siempre el archivo oficial en plantilla).',
'Empezar un hook por "¿Sabías que", "Descubre" o "Imagina".',
],
competitors_never_mention: ['Twenix', 'Preply', 'Italki', 'Open English', "What's Up!"],
emoji_policy:
'Prohibidos los emojis en creatividades de imagen. En copy (primary_text), como mucho uno al inicio y solo en B2C (nunca en B2B).',
testimonials_policy:
'Prohibidos testimonios inventados o con nombre y cargo de personas que no existen. Si no hay un caso real aprobado, no se usa testimonio.',
surface_rules:
'Azul (#2A4D9C) y blanco (#FFFFFF) como base. Amarillo (#FBC02D) solo para resaltar una palabra, un dato o el CTA. Negro (#000000) reservado a mensajes de formalidad o urgencia (FUNDAE que caduca, manifiesto). Ningún otro color.',
typography_headline: 'Poppins Extrabold (siempre en titulares / hook). Nunca otra fuente.',
typography_body: 'Poppins Regular (en cuerpo y línea de apoyo / support).',
image_text_rules:
'Máximo 2 niveles en imagen (titular hook + línea de apoyo support). El titular no supera 8 palabras. Margen seguro mínimo del 8% en todos los lados; nunca texto en el 15% superior ni inferior.',
photography_style:
'Fotografía real de aula, oficina o reunión con luz natural; personas adultas de 25-50 años en contexto de trabajo; sin poses de catálogo. Si es ilustración o composición gráfica, geometría limpia, mucho aire y una sola idea.',
ads_funnel: ['Awareness Reel', 'Consideración carrusel', 'Conversión test'],
linkedin_themes: [
'Inglés profesional',
@@ -878,6 +1073,51 @@ export function parseBrandbook(raw: unknown): Brandbook | null {
source_excerpts: Array.isArray(partial.source_excerpts)
? partial.source_excerpts
: [],
b2b_offering: String(partial.b2b_offering || ''),
b2c_offering: String(partial.b2c_offering || ''),
usp_differentiators: String(partial.usp_differentiators || ''),
current_priorities: String(partial.current_priorities || ''),
website: String(partial.website || ''),
copy_structure_rules: String(partial.copy_structure_rules || ''),
english_terms_policy: String(partial.english_terms_policy || ''),
power_words: arr(partial.power_words),
allowed_ctas: arr(partial.allowed_ctas),
surface_rules: String(partial.surface_rules || ''),
image_text_rules: String(partial.image_text_rules || ''),
typography_headline: String(partial.typography_headline || ''),
typography_body: String(partial.typography_body || ''),
b2b_personas: Array.isArray(partial.b2b_personas)
? partial.b2b_personas.map((p: any) => ({
name: String(p.name || ''),
role: String(p.role || ''),
goal: String(p.goal || ''),
pain_point: String(p.pain_point || ''),
buying_trigger: String(p.buying_trigger || ''),
}))
: [],
b2c_personas: Array.isArray(partial.b2c_personas)
? partial.b2c_personas.map((p: any) => ({
name: String(p.name || ''),
age_range: String(p.age_range || ''),
situation: String(p.situation || ''),
pain_point: String(p.pain_point || ''),
goal: String(p.goal || ''),
}))
: [],
customer_frustrations: String(partial.customer_frustrations || ''),
desired_transformation: String(partial.desired_transformation || ''),
price_and_offer_framing: String(partial.price_and_offer_framing || ''),
objections: Array.isArray(partial.objections)
? partial.objections.map((o: any) => ({
objection: String(o.objection || ''),
rebuttal: String(o.rebuttal || ''),
}))
: [],
winning_hooks: arr(partial.winning_hooks),
preferred_angles: arr(partial.preferred_angles),
emoji_policy: String(partial.emoji_policy || ''),
testimonials_policy: String(partial.testimonials_policy || ''),
competitors_never_mention: arr(partial.competitors_never_mention),
};
}
@@ -1046,3 +1286,356 @@ export function primaryLogo(book: Brandbook): BrandAsset | undefined {
) || book.kit_assets.find((a) => a.kind === 'logo')
);
}
export type OperativeAdsConfig = {
image_ai?: {
photo_style?: string;
rule_1_5_seconds?: string;
forbidden_scenes?: string;
people_count_rules?: string;
negative_space_prompt?: string;
};
campaign_rules?: {
b2c_detection_rules?: string;
b2b_detection_rules?: string;
fallback_audience_rule?: string;
extra_claude_instructions?: string;
};
};
export function compileClientSkillFromBrandbook(
book: Brandbook,
technicalOps?: OperativeAdsConfig,
clientName?: string,
slug?: string
): string {
const name = clientName || book.brand_name || 'Cliente';
const cSlug = slug || name.toLowerCase().replace(/[^a-z0-9]+/g, '-');
const lines: string[] = [];
lines.push(`# Skill de cliente · ${name} (slug: ${cSlug})`);
lines.push('');
lines.push(
`> Esta skill define cómo habla y cómo se ve ${name} en cualquier creatividad. Se inyecta como contexto fijo a Claude en cada petición de relevo. Si algo aquí contradice un brief puntual, manda esta skill.`
);
lines.push('');
// 1. Quién es
lines.push('## Quién es');
lines.push('');
const summary =
book.elevator_pitch ||
book.mission ||
book.positioning ||
`${name} — actividad y propuesta de valor.`;
lines.push(summary);
lines.push('');
if (book.b2b_offering) {
lines.push(`- **B2B (empresas):** ${book.b2b_offering}`);
}
if (book.b2c_offering) {
lines.push(`- **B2C (particulares):** ${book.b2c_offering}`);
}
const usps =
book.usp_differentiators || book.differentiators?.join(' · ') || '';
if (usps) {
lines.push(`- **Diferenciación clave:** ${usps}`);
}
if (book.current_priorities) {
lines.push(
`- **Prioridades actuales y foco comercial:** ${book.current_priorities}`
);
}
const priceFraming =
book.price_and_offer_framing || book.pricing_notes || '';
if (priceFraming) {
lines.push(`- **Enfoque de precio / oferta:** ${priceFraming}`);
}
if (book.website) {
lines.push('');
lines.push(`Web: ${book.website}`);
}
lines.push('');
// 2. A quién le hablamos
lines.push('## A quién le hablamos');
lines.push('');
if (book.b2b_personas && book.b2b_personas.length > 0) {
lines.push('**B2B**');
for (const p of book.b2b_personas) {
const details = [p.role, p.goal, p.pain_point, p.buying_trigger]
.filter(Boolean)
.join('. ');
lines.push(`- *${p.name}* — ${details}`);
}
lines.push('');
}
if (book.b2c_personas && book.b2c_personas.length > 0) {
lines.push('**B2C**');
for (const p of book.b2c_personas) {
const details = [p.age_range, p.situation, p.pain_point, p.goal]
.filter(Boolean)
.join('. ');
lines.push(`- *${p.name}* — ${details}`);
}
lines.push('');
}
if (
(!book.b2b_personas || book.b2b_personas.length === 0) &&
(!book.b2c_personas || book.b2c_personas.length === 0) &&
book.personas &&
book.personas.length > 0
) {
for (const p of book.personas) {
const details = [p.description, p.pains, p.goals, p.channels]
.filter(Boolean)
.join('. ');
lines.push(`- *${p.name}* — ${details}`);
}
lines.push('');
}
const frust =
book.customer_frustrations ||
(book.barriers?.length ? book.barriers.join('; ') : '');
if (frust) {
lines.push(`- **Frustración principal previa:** ${frust}`);
}
const transf = book.desired_transformation || book.brand_promise || '';
if (transf) {
lines.push(`- **Transformación que buscan:** ${transf}`);
}
lines.push('');
// 3. Cómo habla
lines.push('## Cómo habla');
lines.push('');
if (book.tone) lines.push(`- **Tono:** ${book.tone}`);
const posture =
book.positioning ||
(book.personality?.length ? book.personality.join(', ') : '');
if (posture) lines.push(`- **Postura:** ${posture}`);
const langPolicy =
book.languages && book.languages.length > 0
? book.languages.includes('es')
? 'Español de España, tratamiento de tú.'
: book.languages.join(', ')
: 'Español de España, tratamiento de tú.';
lines.push(`- **Idioma y tratamiento:** ${langPolicy}`);
const copyRules =
book.copy_structure_rules ||
(book.voice_do?.length
? book.voice_do.join('; ')
: 'Frases cortas. Un dato concreto vale más que un adjetivo.');
lines.push(`- **Estructura y estilo:** ${copyRules}`);
const englishPolicy =
book.english_terms_policy ||
(book.vocabulary_avoid?.length
? `Evitar términos y jerga en inglés sin traducción: ${book.vocabulary_avoid.join(', ')}`
: '');
if (englishPolicy) {
lines.push(`- **Idiomas / anglicismos en el copy:** ${englishPolicy}`);
}
const powers =
book.power_words && book.power_words.length > 0
? book.power_words
: book.vocabulary_prefer;
if (powers && powers.length > 0) {
lines.push(`- **Conceptos fuerza:** ${powers.join(' · ')}`);
}
const ctas =
book.allowed_ctas && book.allowed_ctas.length > 0
? book.allowed_ctas
: book.ads_ctas;
if (ctas && ctas.length > 0) {
lines.push(`- **CTAs permitidos:** ${ctas.join(', ')}`);
}
lines.push('');
// 4. Argumentos y objeciones
const winArgs = book.ads_usps?.length
? book.ads_usps
: book.proof_points?.length
? book.proof_points
: book.differentiators;
if (winArgs && winArgs.length > 0) {
lines.push('### Argumentos que funcionan');
winArgs.forEach((arg, idx) => {
lines.push(`${idx + 1}. ${arg}`);
});
lines.push('');
}
if (book.objections && book.objections.length > 0) {
lines.push('### Objeciones a desactivar');
for (const obj of book.objections) {
lines.push(`- "${obj.objection}" → ${obj.rebuttal}`);
}
lines.push('');
} else if (book.ads_objections && book.ads_objections.length > 0) {
lines.push('### Objeciones a desactivar');
for (const obj of book.ads_objections) {
lines.push(`- "${obj}"`);
}
lines.push('');
}
if (book.proof_points && book.proof_points.length > 0) {
lines.push('### Pruebas y datos reales verificados');
for (const pt of book.proof_points) {
lines.push(`- ${pt}`);
}
lines.push('');
}
const angles =
book.preferred_angles && book.preferred_angles.length > 0
? book.preferred_angles
: book.ads_angles;
if (angles && angles.length > 0) {
lines.push(
`### Ángulos recomendados para rotar\n- ${angles.join(' · ')}\n`
);
}
// 5. Prohibido
lines.push('## Prohibido');
const forbidden =
book.claims_forbidden && book.claims_forbidden.length > 0
? book.claims_forbidden
: [
'Promesas irreales o plazos garantizados sin base.',
'Fotografía genérica con aspecto artificial.',
'Deformar o recrear el logo mediante IA.',
];
for (const item of forbidden) {
lines.push(`- ${item}`);
}
if (
book.competitors_never_mention &&
book.competitors_never_mention.length > 0
) {
lines.push(
`- Prohibido mencionar a competidores (${book.competitors_never_mention.join(', ')}).`
);
}
if (book.emoji_policy) {
lines.push(`- **Emojis:** ${book.emoji_policy}`);
}
if (book.testimonials_policy) {
lines.push(`- **Testimonios:** ${book.testimonials_policy}`);
}
lines.push('');
// 6. Identidad visual
lines.push('## Identidad visual');
lines.push('');
if (book.colors && book.colors.length > 0) {
const paletteStr = book.colors
.map((c) => `${c.name} \`${c.hex}\` (${c.usage || c.role})`)
.join(' · ');
lines.push(`- **Paleta exacta y única:** ${paletteStr}`);
}
if (book.surface_rules) {
lines.push(`- **Uso de superficies:** ${book.surface_rules}`);
}
const headFont =
book.typography_headline ||
book.fonts_kit?.find(
(f) => f.role === 'headline' || f.role === 'display'
)?.family ||
'N/A';
const bodyFont =
book.typography_body ||
book.fonts_kit?.find((f) => f.role === 'body')?.family ||
'N/A';
if (headFont !== 'N/A' || bodyFont !== 'N/A') {
lines.push(
`- **Tipografía:** Titulares: ${headFont}. Cuerpo/Apoyo: ${bodyFont}.`
);
}
const logoText =
book.logo_usage ||
'Logo oficial de marca con área de protección respetada en esquina; nunca centrado ni dominando.';
lines.push(`- **Logo:** ${logoText}`);
const photoStyle =
technicalOps?.image_ai?.photo_style ||
book.photography_style ||
'Fotografía realista con luz natural, personas reales en contexto del servicio, sin poses forzadas.';
lines.push(`- **Estilo de imagen:** ${photoStyle}`);
const rule15 =
technicalOps?.image_ai?.rule_1_5_seconds ||
'La escena debe mostrar claramente el producto o servicio en menos de 1,5 segundos sin necesidad de leer el texto.';
lines.push(`- **Regla de lectura en 1,5 s:** ${rule15}`);
const forbiddenScenes =
technicalOps?.image_ai?.forbidden_scenes ||
(book.imagery_dont?.length
? book.imagery_dont.join('; ')
: 'Escenas ambiguas que puedan confundirse con otro sector o stock genérico.');
lines.push(`- **Escenas a evitar en imagen:** ${forbiddenScenes}`);
const peopleCount =
technicalOps?.image_ai?.people_count_rules ||
'1 persona si el mensaje es individual; 2 personas si muestra interacción profesional.';
lines.push(`- **Personas en escena:** ${peopleCount}`);
const negSpace =
technicalOps?.image_ai?.negative_space_prompt ||
'the upper third of the frame is empty wall or sky. no text, no logos, no watermarks, photorealistic, vertical 4:5 composition with clear negative space for a headline.';
lines.push(`- **Espacio negativo (image_prompt):** ${negSpace}`);
const imgText =
book.image_text_rules ||
'Máximo 2 niveles (titular ≤ 8 palabras + apoyo ≤ 12 palabras). Margen mínimo del 8% en todos los lados.';
lines.push(`- **Texto en imagen:** ${imgText}`);
lines.push('');
// 7. Audiencia por campaña
lines.push('## Audiencia por campaña');
lines.push('');
lines.push(
'El brief trae `audience`. Si no viene, se deduce del nombre de campaña:'
);
const b2cDet =
technicalOps?.campaign_rules?.b2c_detection_rules ||
'Campañas B2C: dolor personal, cursos individuales, examen oficial. Habla siempre de "tu".';
lines.push(`- **B2C:** ${b2cDet}`);
const b2bDet =
technicalOps?.campaign_rules?.b2b_detection_rules ||
'Campañas B2B: foco en equipos, formación para empresas, bonificación. Habla siempre de "tu equipo".';
lines.push(`- **B2B:** ${b2bDet}`);
const fallbackAud =
technicalOps?.campaign_rules?.fallback_audience_rule ||
'Si el brief no trae audience y no se puede deducir del nombre de campaña, se pregunta en notes y se genera en B2C.';
lines.push(fallbackAud);
lines.push('');
// 8. Ejemplos de hooks
const hooks =
book.winning_hooks && book.winning_hooks.length > 0
? book.winning_hooks
: book.slogan_variants;
if (hooks && hooks.length > 0) {
lines.push(
'## Ejemplos de hooks que han funcionado (referencia de registro, no para copiar)'
);
for (const h of hooks) {
lines.push(`- "${h}"`);
}
lines.push('');
}
const extra =
technicalOps?.campaign_rules?.extra_claude_instructions?.trim() ||
book.freeform_notes?.trim();
if (extra) {
lines.push('## Instrucciones adicionales específicas del cliente');
lines.push(extra);
lines.push('');
}
return lines.join('\n');
}