diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..7643401 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,80 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +AI-powered video script analysis and generation system. Analyzes TikTok/Reels/Shorts via a 5-step pipeline (extract audio → Whisper transcription → GPT-4o 49-field analysis → vector embeddings → Supabase storage) and generates new scripts using top-performing references. + +## Development Commands + +**Start both services (two terminals required):** + +```bash +# Terminal 1 — Backend (port 3001) +cd backend && npm install && npm run dev + +# Terminal 2 — Frontend (port 5173) +cd frontend && npm install && npm run dev +``` + +The frontend Vite dev server proxies `/api/*` to `http://localhost:3001`, so no CORS issues in development. + +There are no test or lint scripts configured. + +## Architecture + +This is a **monorepo with three layers**: + +### `/frontend` — Vue 3 + Vite + Tailwind +- Single-page app with 7 views (Dashboard, Analysis list/detail/create, Scripts, Generate, Login) +- Routes defined in [frontend/src/router/index.js](frontend/src/router/index.js) +- API calls centralized in [frontend/src/lib/api.js](frontend/src/lib/api.js) +- Auth is a mock Pinia store ([frontend/src/stores/auth.js](frontend/src/stores/auth.js)) with hardcoded credentials — not production-ready +- Design system: dark Obsidian theme. Colors defined in [frontend/tailwind.config.js](frontend/tailwind.config.js) as semantic tokens (`canvas`, `surface`, `ink`, `accent`, etc.). Fonts: Bricolage Grotesque (headlines) + Outfit (body) + +### `/backend` — Express.js (local) + `/api` (Vercel serverless) +Two parallel sets of endpoint files exist: +- `/backend/api/` — used by Express server locally +- `/api/` (root) — Vercel serverless functions for production + +When modifying API logic, **both files must be kept in sync** (or changes made to the root `/api/` file if targeting production). + +Core pipeline modules in `/backend/lib/`: +| Module | Role | +|--------|------| +| `extractor.js` | RapidAPI Social Download → audio URL (TikTok/Reels/Shorts) | +| `transcriptor.js` | Whisper-1 → text transcript | +| `analizador.js` | GPT-4o → 49-field JSON analysis (storytelling, Cialdini, neuromarketing) | +| `validador.js` | Zod schema validation of GPT-4o output | +| `embeddings.js` | OpenAI embeddings → pgvector | +| `generador.js` | GPT-4o script generation from top-scoring references | +| `supabase.js` | Supabase client (SERVICE_ROLE_KEY — bypasses RLS) | + +### `/database` — Supabase PostgreSQL + pgvector +Migrations must be applied in order in the Supabase SQL console: +`01_schema → 02_funciones → 03_rls → 04_datos_prueba → 05_analisis_extendido → 06_guiones_generados → 07_diagnostico_contexto` + +Two primary tables: +- **`guiones`** — analyzed scripts, ~49 fields including enums, Cialdini booleans, psychographic scores (1-100), and a `embedding_vector` pgvector column +- **`guiones_generados`** — AI-generated scripts linked to `guiones` references via `referencias_ids UUID[]` + +## Environment Variables + +Create `/backend/.env`: +``` +OPENAI_API_KEY=... +RAPIDAPI_KEY=... # Social Download All In One API +SUPABASE_URL=... +SUPABASE_SERVICE_ROLE_KEY=... +PORT=3001 +``` + +For Vercel production, these same variables must be set in the Vercel project dashboard (the `/api/*.js` functions read from `process.env`). + +## Key Constraints + +- **Dual file sync**: The `/api/*.js` (Vercel) and `/backend/api/*.js` (Express) files implement the same logic — they diverged in past fixes. Always check both when debugging endpoint behavior. +- **No auth on backend endpoints**: API routes have no authentication middleware. Security relies on Supabase RLS + CORS. The service role key bypasses RLS, so backend lib files must never be exposed client-side. +- **Vercel function timeout**: Set to 60s in `vercel.json`. The full analysis pipeline (extract + transcribe + GPT-4o) can take 30-50s on long videos. +- **Node 24.x** required for backend (`--watch` flag in dev script). diff --git a/backend/api/analizar.js b/backend/api/analizar.js index ea9697f..7265dd4 100644 --- a/backend/api/analizar.js +++ b/backend/api/analizar.js @@ -36,8 +36,10 @@ export default async function handler(req, res) { contexto_video = '', } = req.body - if (!url) return res.status(400).json({ error: 'El campo "url" es requerido' }) - if (!niche) return res.status(400).json({ error: 'El campo "niche" es requerido' }) + if (!url) return res.status(400).json({ error: 'El campo "url" es requerido' }) + if (!niche) return res.status(400).json({ error: 'El campo "niche" es requerido' }) + if (!vistas || Number(vistas) <= 0) return res.status(400).json({ error: 'El campo "vistas" es requerido y debe ser mayor a 0' }) + if (!likes || Number(likes) <= 0) return res.status(400).json({ error: 'El campo "likes" es requerido y debe ser mayor a 0' }) const URL_SOPORTADAS = /^https?:\/\/(www\.)?(tiktok\.com|vm\.tiktok\.com|instagram\.com|youtube\.com|youtu\.be)/ if (!URL_SOPORTADAS.test(url)) { diff --git a/backend/api/generar.js b/backend/api/generar.js index 2c2f482..9fb457d 100644 --- a/backend/api/generar.js +++ b/backend/api/generar.js @@ -48,7 +48,8 @@ export default async function handler(req, res) { `) .eq('procesado_ok', true) .eq('niche', niche) - .order('score_virabilidad', { ascending: false }) + .order('likes', { ascending: false }) + .order('vistas', { ascending: false }) .limit(num_referencias) if (plataforma) query = query.eq('plataforma', plataforma) diff --git a/backend/server.js b/backend/server.js index a5d395d..c730a84 100644 --- a/backend/server.js +++ b/backend/server.js @@ -114,8 +114,10 @@ app.post('/api/analizar', async (req, res) => { contexto_video = '', } = req.body - if (!url) return res.status(400).json({ error: 'El campo "url" es requerido' }) - if (!niche) return res.status(400).json({ error: 'El campo "niche" es requerido' }) + if (!url) return res.status(400).json({ error: 'El campo "url" es requerido' }) + if (!niche) return res.status(400).json({ error: 'El campo "niche" es requerido' }) + if (!vistas || Number(vistas) <= 0) return res.status(400).json({ error: 'El campo "vistas" es requerido y debe ser mayor a 0' }) + if (!likes || Number(likes) <= 0) return res.status(400).json({ error: 'El campo "likes" es requerido y debe ser mayor a 0' }) const URL_SOPORTADAS = /^https?:\/\/(www\.)?(tiktok\.com|vm\.tiktok\.com|instagram\.com|youtube\.com|youtu\.be)/ if (!URL_SOPORTADAS.test(url)) { @@ -260,7 +262,8 @@ app.post('/api/generar', async (req, res) => { `) .eq('procesado_ok', true) .eq('niche', niche) - .order('score_virabilidad', { ascending: false }) + .order('likes', { ascending: false }) + .order('vistas', { ascending: false }) .limit(num_referencias) if (plataforma) query = query.eq('plataforma', plataforma) diff --git a/frontend/src/views/AnalysisCreateView.vue b/frontend/src/views/AnalysisCreateView.vue index 04a3331..92cac8e 100644 --- a/frontend/src/views/AnalysisCreateView.vue +++ b/frontend/src/views/AnalysisCreateView.vue @@ -109,16 +109,16 @@