feat: análisis extendido (10 campos nuevos) + generador de guiones con GPT-4o

Análisis extendido:
- Nuevos campos: apertura_exacta, cierre_exacto, tecnica_retencion, momento_pico_seg
- Copywriting: nivel_consciencia (Schwartz), objecion_principal, avatar_descripcion
- Replicabilidad: ingredientes_clave, replicabilidad, ratio_emocion_logica
- analizador.js: prompt extendido con metodología Schwartz + retención
- validador.js: schema Zod actualizado con 6 nuevos enums
- Migración SQL 05: ALTER TABLE + nuevos ENUMs + índices

Generador de guiones:
- generador.js: lib GPT-4o con temperatura 0.7 y contexto de patrones
- server.js: endpoints POST /api/generar, GET /api/generados, GET /api/generados/:id
- backend/api/generar.js + api/generar.js + api/generados.js: Vercel handlers
- Migración SQL 06: tabla guiones_generados con score_estimado, variantes, notas
- GenerateView.vue: formulario completo + preview del guion con copy al portapapeles
- SideNavBar: nueva entrada "Generar" con ícono auto_fix_high
- Router: ruta /generate → GenerateView
- api.js: api.generar() + api.generados.listar/obtener()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-29 20:52:25 -05:00
parent 78e8e48759
commit 2fc4168301
15 changed files with 1019 additions and 86 deletions

View File

@ -5,25 +5,31 @@
// ============================================================
import { z } from 'zod'
const EstructuraEnum = z.enum(['AIDA','PAS','hero_journey','storybrand','antes_despues','otra'])
const GanchoTipoEnum = z.enum(['pregunta','declaracion_shock','dato_estadistica','historia','controversia','promesa_directa'])
const DesarrolloEnum = z.enum(['problema_solucion','lista','demostracion','testimonio','tutorial','storytelling_puro'])
const CtaTipoEnum = z.enum(['seguir','comentar','compartir','comprar','visitar_link','guardar','ninguno'])
const PacingEnum = z.enum(['lento','medio','rapido','variable'])
const TriggerEnum = z.enum(['miedo','esperanza','curiosidad','ira','orgullo','tristeza','sorpresa','humor'])
const AtencionVisualEnum= z.enum(['zoom_agresivo','corte_rapido','texto_pantalla','cara_camara','broll_dinamico','ninguno'])
const DolorPlacerEnum = z.enum(['apela_dolor','apela_placer','ambos'])
const CargaEnum = z.enum(['baja','media','alta'])
const VelocidadEnum = z.enum(['lenta','normal','rapida','muy_rapida'])
const TonoEnum = z.enum(['educativo','entretenimiento','inspiracional','controversial','informativo','humoristico'])
const PersonaEnum = z.enum(['primera_persona','segunda_persona','tercera_persona','mixta'])
const EspecificidadEnum = z.enum(['generico','especifico','ultra_especifico'])
const EstructuraEnum = z.enum(['AIDA','PAS','hero_journey','storybrand','antes_despues','otra'])
const GanchoTipoEnum = z.enum(['pregunta','declaracion_shock','dato_estadistica','historia','controversia','promesa_directa'])
const DesarrolloEnum = z.enum(['problema_solucion','lista','demostracion','testimonio','tutorial','storytelling_puro'])
const CtaTipoEnum = z.enum(['seguir','comentar','compartir','comprar','visitar_link','guardar','ninguno'])
const PacingEnum = z.enum(['lento','medio','rapido','variable'])
const TriggerEnum = z.enum(['miedo','esperanza','curiosidad','ira','orgullo','tristeza','sorpresa','humor'])
const AtencionVisualEnum = z.enum(['zoom_agresivo','corte_rapido','texto_pantalla','cara_camara','broll_dinamico','ninguno'])
const DolorPlacerEnum = z.enum(['apela_dolor','apela_placer','ambos'])
const CargaEnum = z.enum(['baja','media','alta'])
const VelocidadEnum = z.enum(['lenta','normal','rapida','muy_rapida'])
const TonoEnum = z.enum(['educativo','entretenimiento','inspiracional','controversial','informativo','humoristico'])
const PersonaEnum = z.enum(['primera_persona','segunda_persona','tercera_persona','mixta'])
const EspecificidadEnum = z.enum(['generico','especifico','ultra_especifico'])
const TecnicaRetencionEnum= z.enum(['open_loop','cliffhanger','curiosity_gap','countdown','pregunta_abierta','ninguna'])
const RatioEmocionEnum = z.enum(['emocional','logico','equilibrado'])
const NivelConcienciaEnum = z.enum(['inconsciente','problema_consciente','solucion_consciente','producto_consciente','mas_consciente'])
const ReplicabilidadEnum = z.enum(['alta','media','baja'])
export const AnalisisSchema = z.object({
// Storytelling
estructura_narrativa: EstructuraEnum,
gancho_tipo: GanchoTipoEnum,
gancho_texto: z.string().min(1).max(200),
apertura_exacta: z.string().min(1).max(300),
cierre_exacto: z.string().min(1).max(200),
gancho_duracion_seg: z.number().int().min(0).max(30),
desarrollo_tipo: DesarrolloEnum,
cta_tipo: CtaTipoEnum,
@ -33,6 +39,8 @@ export const AnalisisSchema = z.object({
resolucion: z.string().min(1).max(500),
pacing_ritmo: PacingEnum,
numero_actos: z.number().int().min(1).max(4),
tecnica_retencion: TecnicaRetencionEnum,
momento_pico_seg: z.number().int().min(0).max(600),
// Cialdini
cialdini_reciprocidad: z.boolean(),
@ -57,8 +65,9 @@ export const AnalisisSchema = z.object({
velocidad_locucion: VelocidadEnum,
uso_musica: z.boolean(),
micro_compromisos: z.boolean(),
ratio_emocion_logica: RatioEmocionEnum,
// Contenido
// Contenido + Copywriting
tema_principal: z.string().min(1).max(100),
angulo_unico: z.string().min(1).max(500),
palabras_clave: z.array(z.string()).min(1).max(10),
@ -66,10 +75,15 @@ export const AnalisisSchema = z.object({
persona_narradora: PersonaEnum,
promesa_explicita: z.string().min(1).max(500),
nivel_especificidad: EspecificidadEnum,
nivel_consciencia: NivelConcienciaEnum,
objecion_principal: z.string().max(500).nullable(),
avatar_descripcion: z.string().min(1).max(500),
ingredientes_clave: z.array(z.string()).min(1).max(7),
replicabilidad: ReplicabilidadEnum,
// Métricas calculadas por GPT-4o
// Métricas
score_virabilidad: z.number().int().min(1).max(100),
resumen_patron: z.string().min(10).max(1000),
resumen_patron: z.string().min(10).max(1500),
})
/**