From be69c0aa4898a82c3feb15efcd9a18badb4ec836 Mon Sep 17 00:00:00 2001 From: Hanzo_dev <2002samudiojohan@gmail.com> Date: Sun, 29 Mar 2026 21:44:24 -0500 Subject: [PATCH] =?UTF-8?q?feat:=20contexto=20de=20video,=20an=C3=A1lisis?= =?UTF-8?q?=20extendido=20y=20m=C3=A9tricas=20sociales?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Campo "Contexto del Video" en formulario de análisis (Paso 03) → se pasa a GPT-4o para enriquecer el análisis - 4 nuevos campos de diagnóstico: fortalezas, debilidades, sugerencias_mejora, hashtags_sugeridos (click para copiar) - Vista de detalle: card de métricas sociales (vistas/likes/compartidos con engagement rate calculado) - Muestra contexto original ingresado por el usuario - Migración SQL 07: 5 nuevas columnas en tabla guiones - validador.js: 4 nuevos campos en schema Zod - server.js + api/analizar.js: acepta y guarda contexto_video Co-Authored-By: Claude Sonnet 4.6 --- backend/api/analizar.js | 10 +- backend/lib/analizador.js | 16 ++- backend/lib/validador.js | 6 + backend/server.js | 4 +- .../migrations/07_diagnostico_contexto.sql | 17 +++ frontend/src/views/AnalysisCreateView.vue | 27 ++++- frontend/src/views/AnalysisDetailView.vue | 104 +++++++++++++++++- 7 files changed, 173 insertions(+), 11 deletions(-) create mode 100644 database/migrations/07_diagnostico_contexto.sql diff --git a/backend/api/analizar.js b/backend/api/analizar.js index 7366597..f838cc1 100644 --- a/backend/api/analizar.js +++ b/backend/api/analizar.js @@ -33,6 +33,7 @@ export default async function handler(req, res) { likes = null, compartidos = null, fecha_publicacion = null, + contexto_video = '', } = req.body if (!url) return res.status(400).json({ error: 'El campo "url" es requerido' }) @@ -56,7 +57,7 @@ export default async function handler(req, res) { // ── PASO 3: Analizar con GPT-4o ─────────────────────── paso = 'analisis' - const analisisRaw = await analizarTranscript(transcript, niche, plataforma, duracion) + const analisisRaw = await analizarTranscript(transcript, niche, plataforma, duracion, contexto_video) // ── PASO 4: Validar con Zod ─────────────────────────── paso = 'validacion' @@ -136,6 +137,13 @@ export default async function handler(req, res) { persona_narradora: analisis.persona_narradora, promesa_explicita: analisis.promesa_explicita, nivel_especificidad: analisis.nivel_especificidad, + contexto_video: contexto_video || null, + + // Diagnóstico + fortalezas: analisis.fortalezas, + debilidades: analisis.debilidades, + sugerencias_mejora: analisis.sugerencias_mejora, + hashtags_sugeridos: analisis.hashtags_sugeridos, // Métricas (score_engagement lo calcula el trigger de Supabase) score_virabilidad: analisis.score_virabilidad, diff --git a/backend/lib/analizador.js b/backend/lib/analizador.js index a348a0b..a430468 100644 --- a/backend/lib/analizador.js +++ b/backend/lib/analizador.js @@ -1,7 +1,7 @@ // ============================================================ // ANALIZADOR — GPT-4o // Prompt maestro multidisciplinario: Storytelling + Cialdini -// + Neuropublicidad + Copywriting → JSON de 45 campos +// + Neuropublicidad + Copywriting → JSON de 49 campos // ============================================================ import OpenAI from 'openai' @@ -22,12 +22,17 @@ SOLO devuelve el JSON, sin texto adicional, sin markdown, sin explicaciones.` * @param {string} niche Nicho del video (ej: "fitness", "finanzas") * @param {string} plataforma tiktok | reels | shorts * @param {number} duracion Duración en segundos + * @param {string} contextoVideo Contexto adicional opcional sobre el video * @returns {object} JSON con todos los campos de análisis */ -export async function analizarTranscript(transcript, niche, plataforma, duracion) { +export async function analizarTranscript(transcript, niche, plataforma, duracion, contextoVideo = '') { + const bloqueContexto = contextoVideo + ? `CONTEXTO ADICIONAL DEL VIDEO (úsalo para enriquecer el análisis):\n"""\n${contextoVideo}\n"""\n\n` + : '' + const promptUsuario = `Analiza este video de ${plataforma} de ${duracion} segundos del nicho "${niche}". -TRANSCRIPCIÓN: +${bloqueContexto}TRANSCRIPCIÓN: """ ${transcript} """ @@ -88,6 +93,11 @@ Devuelve EXACTAMENTE este JSON con los valores que correspondan: "ingredientes_clave": ["", "", ""], "replicabilidad": "", + "fortalezas": ["", "", ""], + "debilidades": ["", ""], + "sugerencias_mejora": ["", "", ""], + "hashtags_sugeridos": ["", "", "", "", "", "", ""], + "score_virabilidad": , "resumen_patron": "" }` diff --git a/backend/lib/validador.js b/backend/lib/validador.js index d2feee2..adc5648 100644 --- a/backend/lib/validador.js +++ b/backend/lib/validador.js @@ -81,6 +81,12 @@ export const AnalisisSchema = z.object({ ingredientes_clave: z.array(z.string()).min(1).max(7), replicabilidad: ReplicabilidadEnum, + // Diagnóstico y mejora + fortalezas: z.array(z.string()).min(1).max(5), + debilidades: z.array(z.string()).min(1).max(5), + sugerencias_mejora: z.array(z.string()).min(1).max(5), + hashtags_sugeridos: z.array(z.string()).min(1).max(10), + // Métricas score_virabilidad: z.number().int().min(1).max(100), resumen_patron: z.string().min(10).max(1500), diff --git a/backend/server.js b/backend/server.js index 86d528a..bb7dbbc 100644 --- a/backend/server.js +++ b/backend/server.js @@ -111,6 +111,7 @@ app.post('/api/analizar', async (req, res) => { competidor_referente = false, vistas = null, likes = null, compartidos = null, fecha_publicacion = null, + contexto_video = '', } = req.body if (!url) return res.status(400).json({ error: 'El campo "url" es requerido' }) @@ -135,7 +136,7 @@ app.post('/api/analizar', async (req, res) => { paso = 'analisis' console.log(`[3/5] Analizando con GPT-4o...`) - const analisisRaw = await analizarTranscript(transcript, niche, plataforma, duracion) + const analisisRaw = await analizarTranscript(transcript, niche, plataforma, duracion, contexto_video) paso = 'validacion' console.log(`[4/5] Validando schema...`) @@ -152,6 +153,7 @@ app.post('/api/analizar', async (req, res) => { proyecto_nombre, competidor_referente, url_origen: url, plataforma, duracion_segundos: duracion, vistas, likes, compartidos, fecha_publicacion, + contexto_video: contexto_video || null, ...analisis, transcript, embedding_vector: `[${vector.join(',')}]`, diff --git a/database/migrations/07_diagnostico_contexto.sql b/database/migrations/07_diagnostico_contexto.sql new file mode 100644 index 0000000..d868503 --- /dev/null +++ b/database/migrations/07_diagnostico_contexto.sql @@ -0,0 +1,17 @@ +-- ============================================================ +-- MIGRACIÓN 07 — Contexto, Diagnóstico y Hashtags +-- Ejecutar en Supabase SQL Editor después de la migración 06 +-- ============================================================ + +alter table guiones + add column if not exists contexto_video text, + add column if not exists fortalezas text[], + add column if not exists debilidades text[], + add column if not exists sugerencias_mejora text[], + add column if not exists hashtags_sugeridos text[]; + +comment on column guiones.contexto_video is 'Contexto o idea del video ingresado por el usuario antes del análisis'; +comment on column guiones.fortalezas is 'Lo que el video hace especialmente bien (generado por GPT-4o)'; +comment on column guiones.debilidades is 'Áreas de mejora detectadas por GPT-4o'; +comment on column guiones.sugerencias_mejora is 'Sugerencias accionables para mejorar el video'; +comment on column guiones.hashtags_sugeridos is 'Hashtags sugeridos por GPT-4o para maximizar alcance'; diff --git a/frontend/src/views/AnalysisCreateView.vue b/frontend/src/views/AnalysisCreateView.vue index b763783..a534a06 100644 --- a/frontend/src/views/AnalysisCreateView.vue +++ b/frontend/src/views/AnalysisCreateView.vue @@ -99,6 +99,30 @@ + + +
+
+ 03 +

Contexto del Video (opcional)

+
+ +
+

+ Describe de qué trata el video, cuál era la intención del creador, o cualquier detalle que la transcripción sola no captura (ej. tono irónico, contexto de tendencia, referencia cultural). GPT-4o usará esto para enriquecer el análisis. +

+
+ lightbulb + +
+
+
@@ -176,7 +200,8 @@ const form = ref({ compartidos: null, cliente_id: null, proyecto_nombre: '', - competidor_referente: false + competidor_referente: false, + contexto_video: '' }) const pasosVisibles = [ diff --git a/frontend/src/views/AnalysisDetailView.vue b/frontend/src/views/AnalysisDetailView.vue index 99aa686..8b8041e 100644 --- a/frontend/src/views/AnalysisDetailView.vue +++ b/frontend/src/views/AnalysisDetailView.vue @@ -43,6 +43,34 @@
+ +
+

+ bar_chart Métricas del Video +

+
+
+ visibility +

Vistas

+

{{ formatNum(guion.vistas) }}

+
+
+ favorite +

Likes

+

{{ formatNum(guion.likes) }}

+
+
+ share +

Compartidos

+

{{ formatNum(guion.compartidos) }}

+
+
+
+ Engagement Rate + {{ (guion.score_engagement * 1).toFixed(2) }}% +
+
+
@@ -59,15 +87,11 @@ / 100
-
+

Cialdini

{{ guion.score_cialdini ?? 0 }}/7

-
-

Engagement

-

{{ guion.score_engagement ? (guion.score_engagement*1).toFixed(2) + '%' : 'N/A' }}

-

Intensidad

{{ guion.intensidad_emocional || 0 }}/10

@@ -281,6 +305,65 @@
+ +
+
+

+ thumb_up Fortalezas +

+
+
+ +

{{ f }}

+
+
+
+
+

+ build Áreas de Mejora +

+
+
+ +

{{ d }}

+
+
+
+

Sugerencias

+
+
+ {{ i + 1 }}. +

{{ s }}

+
+
+
+
+
+ + +
+

+ tag Hashtags Sugeridos +

+
+ #{{ tag.replace(/^#/, '') }} +
+

Haz clic en un hashtag para copiarlo

+
+ + +
+

+ lightbulb Contexto ingresado +

+

{{ guion.contexto_video }}

+
+
@@ -343,6 +426,17 @@ function openUrl(url) { if (url) window.open(url, '_blank') } +function formatNum(n) { + if (!n) return '—' + if (n >= 1_000_000) return (n / 1_000_000).toFixed(1) + 'M' + if (n >= 1_000) return (n / 1_000).toFixed(1) + 'K' + return n.toLocaleString() +} + +function copiarTag(tag) { + navigator.clipboard.writeText('#' + tag.replace(/^#/, '')) +} + function plataformaBadge(p) { return { tiktok: 'bg-red-500/20 text-red-400',