feat: centralizar ADN y estilo de marca en Zona de Marca y simplificar panel de creatividades a operativa ads
This commit is contained in:
@@ -29,6 +29,9 @@ import {
|
||||
type BrandbookSectionId,
|
||||
type ColorToken,
|
||||
type FontToken,
|
||||
type CreativeB2BPersona,
|
||||
type CreativeB2CPersona,
|
||||
type CreativeObjection,
|
||||
} from '@/lib/brandbook';
|
||||
import GoogleFontsGallery from '@/components/GoogleFontsGallery';
|
||||
import BrandOverview from '@/components/BrandOverview';
|
||||
@@ -632,6 +635,58 @@ export default function BrandZone({ tenantLabel }: { tenantLabel: string }) {
|
||||
onChange={(e) => patch({ origin_story: e.target.value })}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Sitio Web">
|
||||
<input
|
||||
className={inputCls}
|
||||
value={book.website || ''}
|
||||
placeholder="ej. blueparrotschool.com"
|
||||
onChange={(e) => patch({ website: e.target.value })}
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
label="Oferta y programas B2B (empresas)"
|
||||
hint="Programas in-company, formación equipos, acreditaciones oficiales"
|
||||
className="sm:col-span-2"
|
||||
>
|
||||
<textarea
|
||||
className={areaCls}
|
||||
value={book.b2b_offering || ''}
|
||||
onChange={(e) => patch({ b2b_offering: e.target.value })}
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
label="Oferta y cursos B2C (particulares)"
|
||||
hint="Cursos adultos, intensivos, certificaciones Cambridge/Aptis, 1to1"
|
||||
className="sm:col-span-2"
|
||||
>
|
||||
<textarea
|
||||
className={areaCls}
|
||||
value={book.b2c_offering || ''}
|
||||
onChange={(e) => patch({ b2c_offering: e.target.value })}
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
label="Diferenciación clave (USPs frente a competencia)"
|
||||
hint="Profesor fijo, sesiones completas, acreditación directa FUNDAE..."
|
||||
className="sm:col-span-2"
|
||||
>
|
||||
<textarea
|
||||
className={areaCls}
|
||||
value={book.usp_differentiators || ''}
|
||||
onChange={(e) => patch({ usp_differentiators: e.target.value })}
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
label="Prioridades actuales y foco comercial"
|
||||
hint="Foco de captación prioritario para este periodo"
|
||||
className="sm:col-span-2"
|
||||
>
|
||||
<textarea
|
||||
className={areaCls}
|
||||
value={book.current_priorities || ''}
|
||||
onChange={(e) => patch({ current_priorities: e.target.value })}
|
||||
/>
|
||||
</Field>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -725,6 +780,48 @@ export default function BrandZone({ tenantLabel }: { tenantLabel: string }) {
|
||||
}
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
label="Estructura y reglas de redacción (Copy Ads & Web)"
|
||||
hint="Longitud, número de frases, estructura hook -> beneficio -> CTA"
|
||||
className="sm:col-span-2"
|
||||
>
|
||||
<textarea
|
||||
className={areaCls}
|
||||
value={book.copy_structure_rules || ''}
|
||||
placeholder="ej. Frases cortas. Un dato concreto vale más que un adjetivo. En primary_text: 2-4 frases, máx. 300 caracteres..."
|
||||
onChange={(e) => patch({ copy_structure_rules: e.target.value })}
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
label="Política de términos en inglés y anglicismos"
|
||||
hint="Términos admitidos vs anglicismos prohibidos tipo 'boost' o 'level up'"
|
||||
className="sm:col-span-2"
|
||||
>
|
||||
<textarea
|
||||
className={areaCls}
|
||||
value={book.english_terms_policy || ''}
|
||||
placeholder="ej. Inglés en el copy: solo nombres propios de exámenes o términos sin traducción natural..."
|
||||
onChange={(e) => patch({ english_terms_policy: e.target.value })}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Conceptos fuerza (Power Words)" hint="Uno por línea">
|
||||
<textarea
|
||||
className={areaCls}
|
||||
value={listToLines(book.power_words || [])}
|
||||
onChange={(e) =>
|
||||
patch({ power_words: linesToList(e.target.value) })
|
||||
}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="CTAs permitidos para Ads / Web" hint="Uno por línea">
|
||||
<textarea
|
||||
className={areaCls}
|
||||
value={listToLines(book.allowed_ctas || [])}
|
||||
onChange={(e) =>
|
||||
patch({ allowed_ctas: linesToList(e.target.value) })
|
||||
}
|
||||
/>
|
||||
</Field>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1031,6 +1128,38 @@ export default function BrandZone({ tenantLabel }: { tenantLabel: string }) {
|
||||
}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Uso de superficies y combinaciones" className="sm:col-span-2">
|
||||
<textarea
|
||||
className={areaCls}
|
||||
value={book.surface_rules || ''}
|
||||
placeholder="ej. Azul (#2A4D9C) y blanco (#FFFFFF) como base. Amarillo (#FBC02D) solo para resaltar una palabra o CTA..."
|
||||
onChange={(e) => patch({ surface_rules: e.target.value })}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Tipografía titulares (Hook)">
|
||||
<input
|
||||
className={inputCls}
|
||||
value={book.typography_headline || ''}
|
||||
placeholder="ej. Poppins Extrabold"
|
||||
onChange={(e) => patch({ typography_headline: e.target.value })}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Tipografía cuerpo / apoyo (Support)">
|
||||
<input
|
||||
className={inputCls}
|
||||
value={book.typography_body || ''}
|
||||
placeholder="ej. Poppins Regular"
|
||||
onChange={(e) => patch({ typography_body: e.target.value })}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Reglas de texto en imagen" className="sm:col-span-2">
|
||||
<textarea
|
||||
className={areaCls}
|
||||
value={book.image_text_rules || ''}
|
||||
placeholder="ej. Máximo 2 niveles (titular ≤ 8 palabras + apoyo ≤ 12 palabras). Margen mínimo del 8% en todos los lados..."
|
||||
onChange={(e) => patch({ image_text_rules: e.target.value })}
|
||||
/>
|
||||
</Field>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@@ -1181,6 +1310,274 @@ export default function BrandZone({ tenantLabel }: { tenantLabel: string }) {
|
||||
onChange={(e) => patch({ customer_journey: e.target.value })}
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
label="Frustración principal previa de la audiencia"
|
||||
hint="Por qué han fracasado con otras alternativas o en qué punto están atascados"
|
||||
className="sm:col-span-2"
|
||||
>
|
||||
<textarea
|
||||
className={areaCls}
|
||||
value={book.customer_frustrations || ''}
|
||||
placeholder="ej. Estancamiento en el nivel intermedio eterno por apps de 15 minutos sin profesor fijo..."
|
||||
onChange={(e) => patch({ customer_frustrations: e.target.value })}
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
label="Transformación deseada (Antes vs Después)"
|
||||
hint="De qué estado evitado pasan a qué resultado concreto"
|
||||
className="sm:col-span-2"
|
||||
>
|
||||
<textarea
|
||||
className={areaCls}
|
||||
value={book.desired_transformation || ''}
|
||||
placeholder="ej. Pasar de evitar hablar en inglés o depender de traductores a negociar con seguridad..."
|
||||
onChange={(e) => patch({ desired_transformation: e.target.value })}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
{/* B2B Personas Card Editor */}
|
||||
<div className="sm:col-span-2 space-y-3 pt-3 border-t border-gray-200">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h4 className="text-xs font-bold uppercase tracking-wider text-gray-800">
|
||||
Buyer Personas B2B (Empresas / Directivos)
|
||||
</h4>
|
||||
<p className="text-[11px] text-gray-500">
|
||||
Cargos, dolores de gestión y disparadores de compra corporativos.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
const list = [...(book.b2b_personas || [])];
|
||||
list.push({
|
||||
name: `Persona B2B #${list.length + 1}`,
|
||||
role: '',
|
||||
goal: '',
|
||||
pain_point: '',
|
||||
buying_trigger: '',
|
||||
});
|
||||
patch({ b2b_personas: list });
|
||||
}}
|
||||
className="inline-flex items-center gap-1 rounded-lg border border-gray-300 bg-white px-2.5 py-1 text-xs font-semibold text-gray-700 hover:bg-gray-50 shadow-sm"
|
||||
>
|
||||
<Plus className="w-3.5 h-3.5 text-gray-500" />
|
||||
Añadir B2B
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{(!book.b2b_personas || book.b2b_personas.length === 0) ? (
|
||||
<p className="text-xs text-gray-400 italic">No hay buyer personas B2B definidas.</p>
|
||||
) : (
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
{book.b2b_personas.map((p, idx) => (
|
||||
<div key={idx} className="rounded-xl border border-gray-200 bg-gray-50/50 p-3 space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<input
|
||||
type="text"
|
||||
value={p.name}
|
||||
onChange={(e) => {
|
||||
const list = [...book.b2b_personas!];
|
||||
list[idx] = { ...list[idx], name: e.target.value };
|
||||
patch({ b2b_personas: list });
|
||||
}}
|
||||
placeholder="Nombre / Identificador"
|
||||
className="font-bold text-xs text-gray-900 bg-transparent border-b border-dashed border-gray-300 focus:outline-none focus:border-brand-500 px-1 py-0.5"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
const list = book.b2b_personas!.filter((_, i) => i !== idx);
|
||||
patch({ b2b_personas: list });
|
||||
}}
|
||||
className="text-gray-400 hover:text-red-500 p-1"
|
||||
>
|
||||
<Trash2 className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-[10px] font-semibold text-gray-500 block">Cargo / Rol</label>
|
||||
<input
|
||||
type="text"
|
||||
value={p.role}
|
||||
onChange={(e) => {
|
||||
const list = [...book.b2b_personas!];
|
||||
list[idx] = { ...list[idx], role: e.target.value };
|
||||
patch({ b2b_personas: list });
|
||||
}}
|
||||
placeholder="ej. Responsable de RRHH o Talento"
|
||||
className="w-full text-xs rounded border border-gray-200 bg-white px-2 py-1"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-[10px] font-semibold text-gray-500 block">Objetivo</label>
|
||||
<input
|
||||
type="text"
|
||||
value={p.goal}
|
||||
onChange={(e) => {
|
||||
const list = [...book.b2b_personas!];
|
||||
list[idx] = { ...list[idx], goal: e.target.value };
|
||||
patch({ b2b_personas: list });
|
||||
}}
|
||||
placeholder="ej. Proveedor llave en mano sin carga administrativa"
|
||||
className="w-full text-xs rounded border border-gray-200 bg-white px-2 py-1"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-[10px] font-semibold text-gray-500 block">Dolor / Frustración</label>
|
||||
<textarea
|
||||
value={p.pain_point}
|
||||
onChange={(e) => {
|
||||
const list = [...book.b2b_personas!];
|
||||
list[idx] = { ...list[idx], pain_point: e.target.value };
|
||||
patch({ b2b_personas: list });
|
||||
}}
|
||||
placeholder="ej. Papeleo FUNDAE, quejas por rotación de profes..."
|
||||
className="w-full text-xs rounded border border-gray-200 bg-white px-2 py-1 min-h-[50px]"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-[10px] font-semibold text-gray-500 block">Disparador de Compra</label>
|
||||
<input
|
||||
type="text"
|
||||
value={p.buying_trigger}
|
||||
onChange={(e) => {
|
||||
const list = [...book.b2b_personas!];
|
||||
list[idx] = { ...list[idx], buying_trigger: e.target.value };
|
||||
patch({ b2b_personas: list });
|
||||
}}
|
||||
placeholder="ej. Gestión FUNDAE directa 100% resuelta"
|
||||
className="w-full text-xs rounded border border-gray-200 bg-white px-2 py-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* B2C Personas Card Editor */}
|
||||
<div className="sm:col-span-2 space-y-3 pt-3 border-t border-gray-200">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h4 className="text-xs font-bold uppercase tracking-wider text-gray-800">
|
||||
Buyer Personas B2C (Particulares / Alumnos)
|
||||
</h4>
|
||||
<p className="text-[11px] text-gray-500">
|
||||
Segmentos de particulares, edad, situación vital, dolor y meta.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
const list = [...(book.b2c_personas || [])];
|
||||
list.push({
|
||||
name: `Persona B2C #${list.length + 1}`,
|
||||
age_range: '',
|
||||
situation: '',
|
||||
pain_point: '',
|
||||
goal: '',
|
||||
});
|
||||
patch({ b2c_personas: list });
|
||||
}}
|
||||
className="inline-flex items-center gap-1 rounded-lg border border-gray-300 bg-white px-2.5 py-1 text-xs font-semibold text-gray-700 hover:bg-gray-50 shadow-sm"
|
||||
>
|
||||
<Plus className="w-3.5 h-3.5 text-gray-500" />
|
||||
Añadir B2C
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{(!book.b2c_personas || book.b2c_personas.length === 0) ? (
|
||||
<p className="text-xs text-gray-400 italic">No hay buyer personas B2C definidas.</p>
|
||||
) : (
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
{book.b2c_personas.map((p, idx) => (
|
||||
<div key={idx} className="rounded-xl border border-gray-200 bg-gray-50/50 p-3 space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<input
|
||||
type="text"
|
||||
value={p.name}
|
||||
onChange={(e) => {
|
||||
const list = [...book.b2c_personas!];
|
||||
list[idx] = { ...list[idx], name: e.target.value };
|
||||
patch({ b2c_personas: list });
|
||||
}}
|
||||
placeholder="Perfil / Segmento"
|
||||
className="font-bold text-xs text-gray-900 bg-transparent border-b border-dashed border-gray-300 focus:outline-none focus:border-brand-500 px-1 py-0.5"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
const list = book.b2c_personas!.filter((_, i) => i !== idx);
|
||||
patch({ b2c_personas: list });
|
||||
}}
|
||||
className="text-gray-400 hover:text-red-500 p-1"
|
||||
>
|
||||
<Trash2 className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div>
|
||||
<label className="text-[10px] font-semibold text-gray-500 block">Rango de Edad</label>
|
||||
<input
|
||||
type="text"
|
||||
value={p.age_range}
|
||||
onChange={(e) => {
|
||||
const list = [...book.b2c_personas!];
|
||||
list[idx] = { ...list[idx], age_range: e.target.value };
|
||||
patch({ b2c_personas: list });
|
||||
}}
|
||||
placeholder="ej. 25-45 años"
|
||||
className="w-full text-xs rounded border border-gray-200 bg-white px-2 py-1"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-[10px] font-semibold text-gray-500 block">Situación</label>
|
||||
<input
|
||||
type="text"
|
||||
value={p.situation}
|
||||
onChange={(e) => {
|
||||
const list = [...book.b2c_personas!];
|
||||
list[idx] = { ...list[idx], situation: e.target.value };
|
||||
patch({ b2c_personas: list });
|
||||
}}
|
||||
placeholder="ej. Opositor / Empleado"
|
||||
className="w-full text-xs rounded border border-gray-200 bg-white px-2 py-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-[10px] font-semibold text-gray-500 block">Dolor / Frustración</label>
|
||||
<textarea
|
||||
value={p.pain_point}
|
||||
onChange={(e) => {
|
||||
const list = [...book.b2c_personas!];
|
||||
list[idx] = { ...list[idx], pain_point: e.target.value };
|
||||
patch({ b2c_personas: list });
|
||||
}}
|
||||
placeholder="ej. Ha fracasado con apps sin feedback ni estructura..."
|
||||
className="w-full text-xs rounded border border-gray-200 bg-white px-2 py-1 min-h-[50px]"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-[10px] font-semibold text-gray-500 block">Meta / Objetivo</label>
|
||||
<input
|
||||
type="text"
|
||||
value={p.goal}
|
||||
onChange={(e) => {
|
||||
const list = [...book.b2c_personas!];
|
||||
list[idx] = { ...list[idx], goal: e.target.value };
|
||||
patch({ b2c_personas: list });
|
||||
}}
|
||||
placeholder="ej. Certificar B2/C1 Cambridge en 6 meses"
|
||||
className="w-full text-xs rounded border border-gray-200 bg-white px-2 py-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1256,6 +1653,18 @@ export default function BrandZone({ tenantLabel }: { tenantLabel: string }) {
|
||||
onChange={(e) => patch({ pricing_notes: e.target.value })}
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
label="Enfoque de precio / oferta para Ads y Funnel"
|
||||
hint="Coste neto, bonificación FUNDAE, evitar descuentos agresivos..."
|
||||
className="sm:col-span-2"
|
||||
>
|
||||
<textarea
|
||||
className={areaCls}
|
||||
value={book.price_and_offer_framing || ''}
|
||||
placeholder="ej. En B2B destacar el coste bonificado vía FUNDAE... En B2C destacar el valor del programa sin descuentos agresivos..."
|
||||
onChange={(e) => patch({ price_and_offer_framing: e.target.value })}
|
||||
/>
|
||||
</Field>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1473,6 +1882,116 @@ export default function BrandZone({ tenantLabel }: { tenantLabel: string }) {
|
||||
onChange={(e) => patch({ landing_notes: e.target.value })}
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
label="Hooks ganadores de referencia"
|
||||
hint="Ejemplos reales que han tenido buen CTR / conversión (uno por línea, para referencia de estilo)"
|
||||
className="sm:col-span-2"
|
||||
>
|
||||
<textarea
|
||||
className={areaCls}
|
||||
value={listToLines(book.winning_hooks || [])}
|
||||
placeholder="ej. Tu equipo no necesita más inglés. Necesita el inglés de su trabajo..."
|
||||
onChange={(e) =>
|
||||
patch({ winning_hooks: linesToList(e.target.value) })
|
||||
}
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
label="Ángulos recomendados para rotar en campañas"
|
||||
hint="Ángulos prioritarios para Claude (uno por línea)"
|
||||
className="sm:col-span-2"
|
||||
>
|
||||
<textarea
|
||||
className={areaCls}
|
||||
value={listToLines(book.preferred_angles || [])}
|
||||
placeholder="ej. objeción tiempo, coste FUNDAE, contraste app vs programa real, especialización sectorial..."
|
||||
onChange={(e) =>
|
||||
patch({ preferred_angles: linesToList(e.target.value) })
|
||||
}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
{/* Objeciones a desactivar con Rebuttal */}
|
||||
<div className="sm:col-span-2 space-y-3 pt-3 border-t border-gray-200">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h4 className="text-xs font-bold uppercase tracking-wider text-gray-800">
|
||||
Objeciones a Desactivar (Objeción → Argumento de Refutación)
|
||||
</h4>
|
||||
<p className="text-[11px] text-gray-500">
|
||||
Utilizadas por Claude para redactar copys de consideración y remarketing que resuelvan dudas del lead.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
const list = [...(book.objections || [])];
|
||||
list.push({ objection: '', rebuttal: '' });
|
||||
patch({ objections: list });
|
||||
}}
|
||||
className="inline-flex items-center gap-1 rounded-lg border border-gray-300 bg-white px-2.5 py-1 text-xs font-semibold text-gray-700 hover:bg-gray-50 shadow-sm"
|
||||
>
|
||||
<Plus className="w-3.5 h-3.5 text-gray-500" />
|
||||
Añadir Objeción
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{(!book.objections || book.objections.length === 0) ? (
|
||||
<p className="text-xs text-gray-400 italic">No hay objeciones estructuradas definidas.</p>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{book.objections.map((o, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className="flex flex-col sm:flex-row items-start sm:items-center gap-2 rounded-xl border border-gray-200 bg-gray-50/60 p-2.5"
|
||||
>
|
||||
<div className="flex-1 w-full sm:w-auto">
|
||||
<label className="text-[10px] font-semibold text-gray-500 block mb-0.5">
|
||||
Objeción del cliente
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={o.objection}
|
||||
onChange={(e) => {
|
||||
const list = [...book.objections!];
|
||||
list[idx] = { ...list[idx], objection: e.target.value };
|
||||
patch({ objections: list });
|
||||
}}
|
||||
placeholder='ej. "Ya usamos una app"'
|
||||
className="w-full text-xs rounded border border-gray-200 bg-white px-2 py-1.5"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-[2] w-full sm:w-auto">
|
||||
<label className="text-[10px] font-semibold text-gray-500 block mb-0.5">
|
||||
Respuesta / Refutación
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={o.rebuttal}
|
||||
onChange={(e) => {
|
||||
const list = [...book.objections!];
|
||||
list[idx] = { ...list[idx], rebuttal: e.target.value };
|
||||
patch({ objections: list });
|
||||
}}
|
||||
placeholder='ej. "La app mantiene, no avanza. Sin profesor fijo ni sesiones de 60 min no se sale del nivel intermedio."'
|
||||
className="w-full text-xs rounded border border-gray-200 bg-white px-2 py-1.5"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
const list = book.objections!.filter((_, i) => i !== idx);
|
||||
patch({ objections: list });
|
||||
}}
|
||||
className="self-end sm:self-center mt-2 sm:mt-4 text-gray-400 hover:text-red-500 p-1"
|
||||
>
|
||||
<Trash2 className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1634,6 +2153,42 @@ export default function BrandZone({ tenantLabel }: { tenantLabel: string }) {
|
||||
onChange={(e) => patch({ image_rights: e.target.value })}
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
label="Competidores que NUNCA se deben nombrar"
|
||||
hint="Marcas de la competencia totalmente vetadas en copy y claims (uno por línea)"
|
||||
className="sm:col-span-2"
|
||||
>
|
||||
<textarea
|
||||
className={areaCls}
|
||||
value={listToLines(book.competitors_never_mention || [])}
|
||||
placeholder="ej. Twenix, Preply, Italki, Open English, What's Up!..."
|
||||
onChange={(e) =>
|
||||
patch({ competitors_never_mention: linesToList(e.target.value) })
|
||||
}
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
label="Política de Emojis"
|
||||
hint="Normas sobre el uso de emojis en creatividades y copies"
|
||||
>
|
||||
<textarea
|
||||
className={areaCls}
|
||||
value={book.emoji_policy || ''}
|
||||
placeholder="ej. Prohibidos los emojis en creatividades de imagen. En copy, como mucho uno al inicio solo en B2C..."
|
||||
onChange={(e) => patch({ emoji_policy: e.target.value })}
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
label="Política de Testimonios y Casos de Estudio"
|
||||
hint="Normas para evitar testimonios falsos o no autorizados"
|
||||
>
|
||||
<textarea
|
||||
className={areaCls}
|
||||
value={book.testimonials_policy || ''}
|
||||
placeholder="ej. Prohibidos testimonios inventados. Si no hay un caso real verificado, no se usa..."
|
||||
onChange={(e) => patch({ testimonials_policy: e.target.value })}
|
||||
/>
|
||||
</Field>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user