{"repo": "angeltrujillomarrero/debelingoconangel", "files": {"api/ai.js": {"1259b82297b36f73fbe70d7be5f0d9ddb900fd49": {"t": 1785746018.5390868, "n": 4766, "h": "8bb33852d437839f", "c": "import { verifySecurity } from './_auth.js';\nimport { checkRateLimit } from './_rateLimit.js';\nimport { applySecurityHeaders } from './_cors.js';\n\nasync function callOpenRouter(prompt, apiKey) {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 7000);\n\n try {\n const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {\n method: 'POST',\n signal: controller.signal,\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`,\n 'HTTP-Referer': 'https://de-belingo-con-angel.vercel.app',\n 'X-Title': 'De Belingo con Angel',\n },\n body: JSON.stringify({\n model: 'openrouter/free',\n messages: [\n { role: 'system', content: 'Debes indicar siempre qué orquesta está en cada sitio basándote en el listado. Sé exacto, concreto y al grano. Al final de tu respuesta añade siempre \"(S)\".' },\n { role: 'user', content: prompt }\n ],\n temperature: 0.7,\n max_tokens: 350,\n }),\n });\n clearTimeout(timeoutId);\n if (!response.ok) throw new Error('OpenRouter not ok');\n const data = await response.json();\n return data.choices?.[0]?.message?.content || null;\n } catch (e) {\n clearTimeout(timeoutId);\n return null;\n }\n}\n\nasync function callDeepSeek(prompt, apiKey) {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 7000);\n\n try {\n const response = await fetch('https://api.deepseek.com/v1/chat/completions', {\n method: 'POST',\n signal: controller.signal,\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`,\n },\n body: JSON.stringify({\n model: 'deepseek-v4-flash',\n messages: [\n { role: 'system', content: 'Debes indicar siempre qué orquesta está en cada sitio basándote en el listado. Sé exacto, concreto y al grano. Al final de tu respuesta añade siempre \"(M)\".' },\n { role: 'user', content: prompt }\n ],\n temperature: 0.7,\n max_tokens: 350,\n }),\n });\n clearTimeout(timeoutId);\n if (!response.ok) throw new Error('DeepSeek not ok');\n const data = await response.json();\n return data.choices?.[0]?.message?.content || null;\n } catch (e) {\n clearTimeout(timeoutId);\n return null;\n }\n}\n\nexport default async function handler(req, res) {\n if (applySecurityHeaders(req, res)) return;\n if (req.method !== 'POST') return res.status(405).json({ error: 'Method not allowed' });\n\n try {\n const { prompt } = req.body;\n if (!prompt) return res.status(400).json({ error: 'Prompt missing' });\n\n // 1. Auth + Rate Limits in parallel (~1-2s max)\n const userIP = req.headers['x-forwarded-for'] || req.socket.remoteAddress || 'unknown';\n const [authResult, globalLimit, ipLimit] = await Promise.all([\n verifySecurity(req),\n checkRateLimit('angel-ia-global', 120, 60 * 60 * 1000),\n checkRateLimit(`angel-ia-ip:${userIP}`, 30, 60 * 60 * 1000)\n ]);\n\n if (authResult.error) return res.status(authResult.status).json({ error: authResult.error });\n if (!globalLimit.allowed) return res.status(429).json({ error: globalLimit.error });\n if (!ipLimit.allowed) return res.status(429).json({ error: 'Límite de mensajes excedido, puntal.' });\n\n // 2. RACE both AIs simultaneously - first to respond wins\n const openRouterKey = process.env.API_OPENROUTER;\n const mistralKey = process.env.API_TOLETE;\n\n const results = await Promise.allSettled([\n callOpenRouter(prompt, openRouterKey),\n callDeepSeek(prompt, mistralKey)\n ]);\n\n // Pick the first successful, non-null result\n let aiResponse = null;\n for (const result of results) {\n if (result.status === 'fulfilled' && result.value) {\n aiResponse = result.value;\n break;\n }\n }\n\n if (aiResponse) {\n return res.status(200).json({ response: aiResponse });\n } else {\n return res.status(503).json({ error: 'Ninguna IA respondió a tiempo. ¡Reintenta, puntal!' });\n }\n\n } catch (error) {\n console.error('Server error:', error);\n return res.status(500).json({ error: 'Internal server error' });\n }\n}\n"}, "85b63508c4b9dfa2b9bb1882a4caf94f962c5b72": {"t": 1785758825.510505, "n": 4766, "h": "8bb33852d437839f", "c": "import { verifySecurity } from './_auth.js';\nimport { checkRateLimit } from './_rateLimit.js';\nimport { applySecurityHeaders } from './_cors.js';\n\nasync function callOpenRouter(prompt, apiKey) {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 7000);\n\n try {\n const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {\n method: 'POST',\n signal: controller.signal,\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`,\n 'HTTP-Referer': 'https://de-belingo-con-angel.vercel.app',\n 'X-Title': 'De Belingo con Angel',\n },\n body: JSON.stringify({\n model: 'openrouter/free',\n messages: [\n { role: 'system', content: 'Debes indicar siempre qué orquesta está en cada sitio basándote en el listado. Sé exacto, concreto y al grano. Al final de tu respuesta añade siempre \"(S)\".' },\n { role: 'user', content: prompt }\n ],\n temperature: 0.7,\n max_tokens: 350,\n }),\n });\n clearTimeout(timeoutId);\n if (!response.ok) throw new Error('OpenRouter not ok');\n const data = await response.json();\n return data.choices?.[0]?.message?.content || null;\n } catch (e) {\n clearTimeout(timeoutId);\n return null;\n }\n}\n\nasync function callDeepSeek(prompt, apiKey) {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 7000);\n\n try {\n const response = await fetch('https://api.deepseek.com/v1/chat/completions', {\n method: 'POST',\n signal: controller.signal,\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`,\n },\n body: JSON.stringify({\n model: 'deepseek-v4-flash',\n messages: [\n { role: 'system', content: 'Debes indicar siempre qué orquesta está en cada sitio basándote en el listado. Sé exacto, concreto y al grano. Al final de tu respuesta añade siempre \"(M)\".' },\n { role: 'user', content: prompt }\n ],\n temperature: 0.7,\n max_tokens: 350,\n }),\n });\n clearTimeout(timeoutId);\n if (!response.ok) throw new Error('DeepSeek not ok');\n const data = await response.json();\n return data.choices?.[0]?.message?.content || null;\n } catch (e) {\n clearTimeout(timeoutId);\n return null;\n }\n}\n\nexport default async function handler(req, res) {\n if (applySecurityHeaders(req, res)) return;\n if (req.method !== 'POST') return res.status(405).json({ error: 'Method not allowed' });\n\n try {\n const { prompt } = req.body;\n if (!prompt) return res.status(400).json({ error: 'Prompt missing' });\n\n // 1. Auth + Rate Limits in parallel (~1-2s max)\n const userIP = req.headers['x-forwarded-for'] || req.socket.remoteAddress || 'unknown';\n const [authResult, globalLimit, ipLimit] = await Promise.all([\n verifySecurity(req),\n checkRateLimit('angel-ia-global', 120, 60 * 60 * 1000),\n checkRateLimit(`angel-ia-ip:${userIP}`, 30, 60 * 60 * 1000)\n ]);\n\n if (authResult.error) return res.status(authResult.status).json({ error: authResult.error });\n if (!globalLimit.allowed) return res.status(429).json({ error: globalLimit.error });\n if (!ipLimit.allowed) return res.status(429).json({ error: 'Límite de mensajes excedido, puntal.' });\n\n // 2. RACE both AIs simultaneously - first to respond wins\n const openRouterKey = process.env.API_OPENROUTER;\n const mistralKey = process.env.API_TOLETE;\n\n const results = await Promise.allSettled([\n callOpenRouter(prompt, openRouterKey),\n callDeepSeek(prompt, mistralKey)\n ]);\n\n // Pick the first successful, non-null result\n let aiResponse = null;\n for (const result of results) {\n if (result.status === 'fulfilled' && result.value) {\n aiResponse = result.value;\n break;\n }\n }\n\n if (aiResponse) {\n return res.status(200).json({ response: aiResponse });\n } else {\n return res.status(503).json({ error: 'Ninguna IA respondió a tiempo. ¡Reintenta, puntal!' });\n }\n\n } catch (error) {\n console.error('Server error:', error);\n return res.status(500).json({ error: 'Internal server error' });\n }\n}\n"}, "29c116db255ad7060db4492b0f073f2f81c3c720": {"t": 1785758827.224715, "n": 4765, "h": "938f5c299324380d", "c": "import { verifySecurity } from './_auth.js';\nimport { checkRateLimit } from './_rateLimit.js';\nimport { applySecurityHeaders } from './_cors.js';\n\nasync function callOpenRouter(prompt, apiKey) {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 7000);\n\n try {\n const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {\n method: 'POST',\n signal: controller.signal,\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`,\n 'HTTP-Referer': 'https://de-belingo-con-angel.vercel.app',\n 'X-Title': 'De Belingo con Angel',\n },\n body: JSON.stringify({\n model: 'openrouter/free',\n messages: [\n { role: 'system', content: 'Debes indicar siempre qué orquesta está en cada sitio basándote en el listado. Sé exacto, concreto y al grano. Al final de tu respuesta añade siempre \"(S)\".' },\n { role: 'user', content: prompt }\n ],\n temperature: 0.7,\n max_tokens: 350,\n }),\n });\n clearTimeout(timeoutId);\n if (!response.ok) throw new Error('OpenRouter not ok');\n const data = await response.json();\n return data.choices?.[0]?.message?.content || null;\n } catch (e) {\n clearTimeout(timeoutId);\n return null;\n }\n}\n\nasync function callMistral(prompt, apiKey) {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 7000);\n\n try {\n const response = await fetch('https://api.mistral.ai/v1/chat/completions', {\n method: 'POST',\n signal: controller.signal,\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`,\n },\n body: JSON.stringify({\n model: 'mistral-medium-latest',\n messages: [\n { role: 'system', content: 'Debes indicar siempre qué orquesta está en cada sitio basándote en el listado. Sé exacto, concreto y al grano. Al final de tu respuesta añade siempre \"(M)\".' },\n { role: 'user', content: prompt }\n ],\n temperature: 0.7,\n max_tokens: 350,\n }),\n });\n clearTimeout(timeoutId);\n if (!response.ok) throw new Error('Mistral not ok');\n const data = await response.json();\n return data.choices?.[0]?.message?.content || null;\n } catch (e) {\n clearTimeout(timeoutId);\n return null;\n }\n}\n\nexport default async function handler(req, res) {\n if (applySecurityHeaders(req, res)) return;\n if (req.method !== 'POST') return res.status(405).json({ error: 'Method not allowed' });\n\n try {\n const { prompt } = req.body;\n if (!prompt) return res.status(400).json({ error: 'Prompt missing' });\n\n // 1. Auth + Rate Limits in parallel (~1-2s max)\n const userIP = req.headers['x-forwarded-for'] || req.socket.remoteAddress || 'unknown';\n const [authResult, globalLimit, ipLimit] = await Promise.all([\n verifySecurity(req),\n checkRateLimit('angel-ia-global', 120, 60 * 60 * 1000),\n checkRateLimit(`angel-ia-ip:${userIP}`, 30, 60 * 60 * 1000)\n ]);\n\n if (authResult.error) return res.status(authResult.status).json({ error: authResult.error });\n if (!globalLimit.allowed) return res.status(429).json({ error: globalLimit.error });\n if (!ipLimit.allowed) return res.status(429).json({ error: 'Límite de mensajes excedido, puntal.' });\n\n // 2. RACE both AIs simultaneously - first to respond wins\n const openRouterKey = process.env.API_OPENROUTER;\n const mistralKey = process.env.API_TOLETE;\n\n const results = await Promise.allSettled([\n callOpenRouter(prompt, openRouterKey),\n callMistral(prompt, mistralKey)\n ]);\n\n // Pick the first successful, non-null result\n let aiResponse = null;\n for (const result of results) {\n if (result.status === 'fulfilled' && result.value) {\n aiResponse = result.value;\n break;\n }\n }\n\n if (aiResponse) {\n return res.status(200).json({ response: aiResponse });\n } else {\n return res.status(503).json({ error: 'Ninguna IA respondió a tiempo. ¡Reintenta, puntal!' });\n }\n\n } catch (error) {\n console.error('Server error:', error);\n return res.status(500).json({ error: 'Internal server error' });\n }\n}\n"}, "a63b0542a05d1480bda6ceb9e48be758943061d3": {"t": 1785758828.5087562, "n": 4482, "h": "e4f9cce05a015cd4", "c": "import { verifySecurity } from './_auth.js';\nimport { checkRateLimit } from './_rateLimit.js';\nimport { applySecurityHeaders } from './_cors.js';\n\nasync function callOpenRouter(prompt, apiKey) {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 4500); // 4.5s limit for the first attempt\n\n try {\n const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {\n method: 'POST',\n signal: controller.signal,\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`,\n 'HTTP-Referer': 'https://de-belingo-con-angel.vercel.app',\n 'X-Title': 'De Belingo con Angel',\n },\n body: JSON.stringify({\n model: 'openrouter/free',\n messages: [\n { role: 'system', content: 'Debes indicar siempre qué orquesta está en cada sitio basándote en el listado. Sé exacto, concreto y al grano. Al final de tu respuesta añade siempre \"(S)\".' },\n { role: 'user', content: prompt }\n ],\n temperature: 0.7,\n max_tokens: 350,\n }),\n });\n clearTimeout(timeoutId);\n if (!response.ok) return null;\n const data = await response.json();\n return data.choices?.[0]?.message?.content || null;\n } catch (e) {\n return null;\n }\n}\n\nasync function callMistral(prompt, apiKey) {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 4500); // 4.5s limit for the fallback\n\n try {\n const response = await fetch('https://api.mistral.ai/v1/chat/completions', {\n method: 'POST',\n signal: controller.signal,\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`,\n },\n body: JSON.stringify({\n model: 'mistral-medium-latest',\n messages: [\n { role: 'system', content: 'Debes indicar siempre qué orquesta está en cada sitio basándote en el listado. Sé exacto, concreto y al grano. Al final de tu respuesta añade siempre \"(M)\".' },\n { role: 'user', content: prompt }\n ],\n temperature: 0.7,\n max_tokens: 350,\n }),\n });\n clearTimeout(timeoutId);\n if (!response.ok) return null;\n const data = await response.json();\n return data.choices?.[0]?.message?.content || null;\n } catch (e) {\n return null;\n }\n}\n\nexport default async function handler(req, res) {\n if (applySecurityHeaders(req, res)) return;\n if (req.method !== 'POST') return res.status(405).json({ error: 'Method not allowed' });\n\n try {\n const { prompt } = req.body;\n if (!prompt) return res.status(400).json({ error: 'Prompt missing' });\n\n // 1. Verify Security (DO IT ONCE)\n const { error: authError, status: authStatus } = await verifySecurity(req);\n if (authError) return res.status(authStatus).json({ error: authError });\n\n // 2. Global Rate Limit\n const userIP = req.headers['x-forwarded-for'] || req.socket.remoteAddress || 'unknown';\n const [globalLimit, ipLimit] = await Promise.all([\n checkRateLimit('angel-ia-global', 120, 60 * 60 * 1000),\n checkRateLimit(`angel-ia-ip:${userIP}`, 30, 60 * 60 * 1000)\n ]);\n\n if (!globalLimit.allowed) return res.status(429).json({ error: globalLimit.error });\n if (!ipLimit.allowed) return res.status(429).json({ error: 'Límite de mensajes excedido, puntal.' });\n\n // 3. Try OpenRouter First\n let aiResponse = await callOpenRouter(prompt, process.env.API_OPENROUTER);\n\n // 4. Fallback to Mistral if failed\n if (!aiResponse) {\n console.warn(\"Falling back to Mistral...\");\n aiResponse = await callMistral(prompt, process.env.API_TOLETE);\n }\n\n if (aiResponse) {\n return res.status(200).json({ response: aiResponse });\n } else {\n return res.status(503).json({ error: 'La IA ha tardado demasiado en responder. ¡Reintenta, puntal!' });\n }\n\n } catch (error) {\n console.error('Server error:', error);\n return res.status(500).json({ error: 'Internal server error' });\n }\n}\n"}, "6690821d83e91e4b13bb6fa340bddccc8e4111c8": {"t": 1785758829.6624756, "n": 4478, "h": "f676c5495bbf09f4", "c": "import { verifySecurity } from './_auth.js';\nimport { checkRateLimit } from './_rateLimit.js';\nimport { applySecurityHeaders } from './_cors.js';\n\nasync function callOpenRouter(prompt, apiKey) {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 6500); // 6.5s limit\n\n try {\n const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {\n method: 'POST',\n signal: controller.signal,\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`,\n 'HTTP-Referer': 'https://de-belingo-con-angel.vercel.app',\n 'X-Title': 'De Belingo con Angel',\n },\n body: JSON.stringify({\n model: 'openrouter/free', // Switched to generic free router to solve StepFun issues\n messages: [\n { role: 'system', content: 'Debes indicar siempre qué orquesta está en cada sitio basándote en el listado. Sé exacto, concreto y al grano. Al final de tu respuesta añade siempre \"(S)\".' },\n { role: 'user', content: prompt }\n ],\n temperature: 0.7,\n max_tokens: 500,\n }),\n });\n clearTimeout(timeoutId);\n if (!response.ok) return null;\n const data = await response.json();\n return data.choices?.[0]?.message?.content || null;\n } catch (e) {\n return null;\n }\n}\n\nasync function callMistral(prompt, apiKey) {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 6500);\n\n try {\n const response = await fetch('https://api.mistral.ai/v1/chat/completions', {\n method: 'POST',\n signal: controller.signal,\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`,\n },\n body: JSON.stringify({\n model: 'mistral-medium-latest',\n messages: [\n { role: 'system', content: 'Debes indicar siempre qué orquesta está en cada sitio basándote en el listado. Sé exacto, concreto y al grano. Al final de tu respuesta añade siempre \"(M)\".' },\n { role: 'user', content: prompt }\n ],\n temperature: 0.7,\n max_tokens: 500,\n }),\n });\n clearTimeout(timeoutId);\n if (!response.ok) return null;\n const data = await response.json();\n return data.choices?.[0]?.message?.content || null;\n } catch (e) {\n return null;\n }\n}\n\nexport default async function handler(req, res) {\n if (applySecurityHeaders(req, res)) return;\n if (req.method !== 'POST') return res.status(405).json({ error: 'Method not allowed' });\n\n try {\n const { prompt } = req.body;\n if (!prompt) return res.status(400).json({ error: 'Prompt missing' });\n\n // 1. Verify Security (DO IT ONCE)\n const { error: authError, status: authStatus } = await verifySecurity(req);\n if (authError) return res.status(authStatus).json({ error: authError });\n\n // 2. Global Rate Limit\n const userIP = req.headers['x-forwarded-for'] || req.socket.remoteAddress || 'unknown';\n const [globalLimit, ipLimit] = await Promise.all([\n checkRateLimit('angel-ia-global', 120, 60 * 60 * 1000),\n checkRateLimit(`angel-ia-ip:${userIP}`, 30, 60 * 60 * 1000)\n ]);\n\n if (!globalLimit.allowed) return res.status(429).json({ error: globalLimit.error });\n if (!ipLimit.allowed) return res.status(429).json({ error: 'Límite de mensajes excedido, puntal.' });\n\n // 3. Try OpenRouter First\n let aiResponse = await callOpenRouter(prompt, process.env.API_OPENROUTER);\n\n // 4. Fallback to Mistral if failed\n if (!aiResponse) {\n console.warn(\"Falling back to Mistral...\");\n aiResponse = await callMistral(prompt, process.env.API_TOLETE);\n }\n\n if (aiResponse) {\n return res.status(200).json({ response: aiResponse });\n } else {\n return res.status(503).json({ error: 'Ninguna IA respondió a tiempo. ¡Reintenta, puntal!' });\n }\n\n } catch (error) {\n console.error('Server error:', error);\n return res.status(500).json({ error: 'Internal server error' });\n }\n}\n"}, "37319bc0e6735efa75291b9f3510262a6d655409": {"t": 1785758832.1840944, "n": 4431, "h": "258f0c92c09d4230", "c": "import { verifySecurity } from './_auth.js';\nimport { checkRateLimit } from './_rateLimit.js';\nimport { applySecurityHeaders } from './_cors.js';\n\nasync function callOpenRouter(prompt, apiKey) {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 6500); // 6.5s limit\n\n try {\n const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {\n method: 'POST',\n signal: controller.signal,\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`,\n 'HTTP-Referer': 'https://de-belingo-con-angel.vercel.app',\n 'X-Title': 'De Belingo con Angel',\n },\n body: JSON.stringify({\n model: 'stepfun/step-3.5-flash:free',\n messages: [\n { role: 'system', content: 'Debes indicar siempre qué orquesta está en cada sitio basándote en el listado. Sé exacto, concreto y al grano. Al final de tu respuesta añade siempre \"(S)\".' },\n { role: 'user', content: prompt }\n ],\n temperature: 0.7,\n max_tokens: 500,\n }),\n });\n clearTimeout(timeoutId);\n if (!response.ok) return null;\n const data = await response.json();\n return data.choices?.[0]?.message?.content || null;\n } catch (e) {\n return null;\n }\n}\n\nasync function callMistral(prompt, apiKey) {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 6500);\n\n try {\n const response = await fetch('https://api.mistral.ai/v1/chat/completions', {\n method: 'POST',\n signal: controller.signal,\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`,\n },\n body: JSON.stringify({\n model: 'mistral-medium-latest',\n messages: [\n { role: 'system', content: 'Debes indicar siempre qué orquesta está en cada sitio basándote en el listado. Sé exacto, concreto y al grano. Al final de tu respuesta añade siempre \"(M)\".' },\n { role: 'user', content: prompt }\n ],\n temperature: 0.7,\n max_tokens: 500,\n }),\n });\n clearTimeout(timeoutId);\n if (!response.ok) return null;\n const data = await response.json();\n return data.choices?.[0]?.message?.content || null;\n } catch (e) {\n return null;\n }\n}\n\nexport default async function handler(req, res) {\n if (applySecurityHeaders(req, res)) return;\n if (req.method !== 'POST') return res.status(405).json({ error: 'Method not allowed' });\n\n try {\n const { prompt } = req.body;\n if (!prompt) return res.status(400).json({ error: 'Prompt missing' });\n\n // 1. Verify Security (DO IT ONCE)\n const { error: authError, status: authStatus } = await verifySecurity(req);\n if (authError) return res.status(authStatus).json({ error: authError });\n\n // 2. Global Rate Limit\n const userIP = req.headers['x-forwarded-for'] || req.socket.remoteAddress || 'unknown';\n const [globalLimit, ipLimit] = await Promise.all([\n checkRateLimit('angel-ia-global', 120, 60 * 60 * 1000),\n checkRateLimit(`angel-ia-ip:${userIP}`, 30, 60 * 60 * 1000)\n ]);\n\n if (!globalLimit.allowed) return res.status(429).json({ error: globalLimit.error });\n if (!ipLimit.allowed) return res.status(429).json({ error: 'Límite de mensajes excedido, puntal.' });\n\n // 3. Try OpenRouter First\n let aiResponse = await callOpenRouter(prompt, process.env.API_OPENROUTER);\n\n // 4. Fallback to Mistral if failed\n if (!aiResponse) {\n console.warn(\"Falling back to Mistral...\");\n aiResponse = await callMistral(prompt, process.env.API_TOLETE);\n }\n\n if (aiResponse) {\n return res.status(200).json({ response: aiResponse });\n } else {\n return res.status(503).json({ error: 'Ninguna IA respondió a tiempo. ¡Reintenta, puntal!' });\n }\n\n } catch (error) {\n console.error('Server error:', error);\n return res.status(500).json({ error: 'Internal server error' });\n }\n}\n"}, "68bc5ca5514eae9e91c468e1219c5776875fdebe": {"t": 1785758833.5074525, "n": 4299, "h": "feeb0c19fefb2ee1", "c": "import { verifySecurity } from './_auth.js';\nimport { checkRateLimit } from './_rateLimit.js';\nimport { applySecurityHeaders } from './_cors.js';\n\nasync function callOpenRouter(prompt, apiKey) {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 6500); // 6.5s limit\n\n try {\n const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {\n method: 'POST',\n signal: controller.signal,\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`,\n 'HTTP-Referer': 'https://de-belingo-con-angel.vercel.app',\n 'X-Title': 'De Belingo con Angel',\n },\n body: JSON.stringify({\n model: 'stepfun/step-3.5-flash:free',\n messages: [\n { role: 'system', content: 'Se exacto y concreto y al grano tipo: Te queda mas cerca en tiempo y distancia la verbena.' },\n { role: 'user', content: prompt }\n ],\n temperature: 0.7,\n max_tokens: 500,\n }),\n });\n clearTimeout(timeoutId);\n if (!response.ok) return null;\n const data = await response.json();\n return data.choices?.[0]?.message?.content || null;\n } catch (e) {\n return null;\n }\n}\n\nasync function callMistral(prompt, apiKey) {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 6500);\n\n try {\n const response = await fetch('https://api.mistral.ai/v1/chat/completions', {\n method: 'POST',\n signal: controller.signal,\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`,\n },\n body: JSON.stringify({\n model: 'mistral-medium-latest',\n messages: [\n { role: 'system', content: 'Se exacto y concreto y al grano tipo: Te queda mas cerca en tiempo y distancia la verbena.' },\n { role: 'user', content: prompt }\n ],\n temperature: 0.7,\n max_tokens: 500,\n }),\n });\n clearTimeout(timeoutId);\n if (!response.ok) return null;\n const data = await response.json();\n return data.choices?.[0]?.message?.content || null;\n } catch (e) {\n return null;\n }\n}\n\nexport default async function handler(req, res) {\n if (applySecurityHeaders(req, res)) return;\n if (req.method !== 'POST') return res.status(405).json({ error: 'Method not allowed' });\n\n try {\n const { prompt } = req.body;\n if (!prompt) return res.status(400).json({ error: 'Prompt missing' });\n\n // 1. Verify Security (DO IT ONCE)\n const { error: authError, status: authStatus } = await verifySecurity(req);\n if (authError) return res.status(authStatus).json({ error: authError });\n\n // 2. Global Rate Limit\n const userIP = req.headers['x-forwarded-for'] || req.socket.remoteAddress || 'unknown';\n const [globalLimit, ipLimit] = await Promise.all([\n checkRateLimit('angel-ia-global', 120, 60 * 60 * 1000),\n checkRateLimit(`angel-ia-ip:${userIP}`, 30, 60 * 60 * 1000)\n ]);\n\n if (!globalLimit.allowed) return res.status(429).json({ error: globalLimit.error });\n if (!ipLimit.allowed) return res.status(429).json({ error: 'Límite de mensajes excedido, puntal.' });\n\n // 3. Try OpenRouter First\n let aiResponse = await callOpenRouter(prompt, process.env.API_OPENROUTER);\n\n // 4. Fallback to Mistral if failed\n if (!aiResponse) {\n console.warn(\"Falling back to Mistral...\");\n aiResponse = await callMistral(prompt, process.env.API_TOLETE);\n }\n\n if (aiResponse) {\n return res.status(200).json({ response: aiResponse });\n } else {\n return res.status(503).json({ error: 'Ninguna IA respondió a tiempo. ¡Reintenta, puntal!' });\n }\n\n } catch (error) {\n console.error('Server error:', error);\n return res.status(500).json({ error: 'Internal server error' });\n }\n}\n"}, "face850a7b6caef2d221533e8c697c833f68c8e4": {"t": 1786067542.0355017, "n": 4766, "h": "8bb33852d437839f", "c": "import { verifySecurity } from './_auth.js';\nimport { checkRateLimit } from './_rateLimit.js';\nimport { applySecurityHeaders } from './_cors.js';\n\nasync function callOpenRouter(prompt, apiKey) {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 7000);\n\n try {\n const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {\n method: 'POST',\n signal: controller.signal,\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`,\n 'HTTP-Referer': 'https://de-belingo-con-angel.vercel.app',\n 'X-Title': 'De Belingo con Angel',\n },\n body: JSON.stringify({\n model: 'openrouter/free',\n messages: [\n { role: 'system', content: 'Debes indicar siempre qué orquesta está en cada sitio basándote en el listado. Sé exacto, concreto y al grano. Al final de tu respuesta añade siempre \"(S)\".' },\n { role: 'user', content: prompt }\n ],\n temperature: 0.7,\n max_tokens: 350,\n }),\n });\n clearTimeout(timeoutId);\n if (!response.ok) throw new Error('OpenRouter not ok');\n const data = await response.json();\n return data.choices?.[0]?.message?.content || null;\n } catch (e) {\n clearTimeout(timeoutId);\n return null;\n }\n}\n\nasync function callDeepSeek(prompt, apiKey) {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 7000);\n\n try {\n const response = await fetch('https://api.deepseek.com/v1/chat/completions', {\n method: 'POST',\n signal: controller.signal,\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`,\n },\n body: JSON.stringify({\n model: 'deepseek-v4-flash',\n messages: [\n { role: 'system', content: 'Debes indicar siempre qué orquesta está en cada sitio basándote en el listado. Sé exacto, concreto y al grano. Al final de tu respuesta añade siempre \"(M)\".' },\n { role: 'user', content: prompt }\n ],\n temperature: 0.7,\n max_tokens: 350,\n }),\n });\n clearTimeout(timeoutId);\n if (!response.ok) throw new Error('DeepSeek not ok');\n const data = await response.json();\n return data.choices?.[0]?.message?.content || null;\n } catch (e) {\n clearTimeout(timeoutId);\n return null;\n }\n}\n\nexport default async function handler(req, res) {\n if (applySecurityHeaders(req, res)) return;\n if (req.method !== 'POST') return res.status(405).json({ error: 'Method not allowed' });\n\n try {\n const { prompt } = req.body;\n if (!prompt) return res.status(400).json({ error: 'Prompt missing' });\n\n // 1. Auth + Rate Limits in parallel (~1-2s max)\n const userIP = req.headers['x-forwarded-for'] || req.socket.remoteAddress || 'unknown';\n const [authResult, globalLimit, ipLimit] = await Promise.all([\n verifySecurity(req),\n checkRateLimit('angel-ia-global', 120, 60 * 60 * 1000),\n checkRateLimit(`angel-ia-ip:${userIP}`, 30, 60 * 60 * 1000)\n ]);\n\n if (authResult.error) return res.status(authResult.status).json({ error: authResult.error });\n if (!globalLimit.allowed) return res.status(429).json({ error: globalLimit.error });\n if (!ipLimit.allowed) return res.status(429).json({ error: 'Límite de mensajes excedido, puntal.' });\n\n // 2. RACE both AIs simultaneously - first to respond wins\n const openRouterKey = process.env.API_OPENROUTER;\n const mistralKey = process.env.API_TOLETE;\n\n const results = await Promise.allSettled([\n callOpenRouter(prompt, openRouterKey),\n callDeepSeek(prompt, mistralKey)\n ]);\n\n // Pick the first successful, non-null result\n let aiResponse = null;\n for (const result of results) {\n if (result.status === 'fulfilled' && result.value) {\n aiResponse = result.value;\n break;\n }\n }\n\n if (aiResponse) {\n return res.status(200).json({ response: aiResponse });\n } else {\n return res.status(503).json({ error: 'Ninguna IA respondió a tiempo. ¡Reintenta, puntal!' });\n }\n\n } catch (error) {\n console.error('Server error:', error);\n return res.status(500).json({ error: 'Internal server error' });\n }\n}\n"}}}}