F-002 fix: Remove secrets and externalize config

This commit is contained in:
rikrdo
2026-05-25 08:00:05 +02:00
parent d3a558352d
commit 3d41579ad3
58 changed files with 1192807 additions and 52 deletions

View File

@@ -0,0 +1,269 @@
<?php include ('./inc/header.php'); ?>
<?php
ini_set('max_execution_time', 120);
set_time_limit(120);
$producto = "";
$ean = "";
$autoGenerar = false;
$formato = isset($_POST['formato']) ? $_POST['formato'] : 'plano';
// Si viene por URL
if (isset($_GET['name']) && !empty($_GET['name'])) {
$producto = urldecode($_GET['name']);
$autoGenerar = true;
}
// Si viene por POST
if (isset($_POST['prompt'])) {
$producto = trim($_POST['prompt']);
$autoGenerar = true;
}
// Detectar EAN (8 a 13 dígitos seguidos)
if (preg_match('/\b\d{8,13}\b/', $producto, $matches)) {
$ean = $matches[0];
}
if ($autoGenerar && !empty($producto)) {
// Prompt según formato seleccionado
if ($formato === 'plano') {
$prompt = "
Eres un redactor SEO experto en productos naturales, ecológicos y saludables.
Genera una descripción en texto plano sin formato ni negritas ni guiones, lista para pegar en CKEditor.
Sobre el producto: \"$producto\"
Instrucciones:
1. Prioriza la información del fabricante o distribuidor oficial.
2. Si el producto tiene un código EAN, utilízalo para obtener información nutricional en OpenFoodFacts.
3. No menciones ni enlaces fuentes externas.
4. No inventes datos no verificables.
Estructura del texto:
Descripción:
Ingredientes:
Información nutricional (por 100 g):
Beneficios para la salud:
Por qué deberías probarlo:
Keywords:
Reglas:
- No uses HTML, JSON ni emojis.
- No repitas el nombre del producto en exceso.
";
} else {
$prompt = "
Eres un redactor SEO especializado en productos naturales y saludables.
Genera una descripción optimizada, estructurada y lista para publicación web.
Producto: \"$producto\"
Prioriza datos del fabricante o distribuidor oficial, y si el producto tiene un código EAN, usa OpenFoodFacts para complementar la información nutricional.
No incluyas ni menciones enlaces externos, ni nombres de tiendas online o marketplaces.
Estructura del texto:
### Descripción
Breve texto atractivo sobre origen, uso y beneficios.
### Ingredientes
Lista completa y verificada.
### Información nutricional (por 100 g)
Calorías, grasas, hidratos, azúcares, proteínas, fibra y sal. Indica si provienen del fabricante o fuente pública.
### Beneficios para la salud
Texto breve explicando las propiedades y usos.
### Por qué deberías probarlo
Cierre aspiracional, natural y coherente.
### Keywords
Lista de términos relevantes separados por comas.
Reglas:
- No uses HTML ni JSON.
- No uses emojis.
- No menciones fuentes ni enlaces externos.
";
}
$respuesta = obtener_respuesta($prompt);
// Detectar enlaces válidos (solo OpenFoodFacts o fabricante)
$fuentes = [];
// --- Verificar si el EAN existe realmente en OpenFoodFacts ---
if (!empty($ean)) {
$url_off = "https://world.openfoodfacts.org/product/$ean";
$headers = @get_headers($url_off);
if ($headers && strpos($headers[0], '200') !== false) {
$fuentes[] = "Información verificada en OpenFoodFacts: $url_off";
}
}
// --- Verificar si existe ficha oficial del fabricante (ejemplo: Terpenic Labs) ---
$producto_slug = strtolower(str_replace(' ', '-', $producto));
$url_fabricante = "https://www.terpenic.com/product-page/" . urlencode($producto_slug);
$headers = @get_headers($url_fabricante);
if ($headers && strpos($headers[0], '200') !== false) {
$fuentes[] = "Ficha oficial del fabricante: $url_fabricante";
}
// --- Agregar los enlaces válidos al final del texto (copiable) ---
if (!empty($fuentes)) {
$respuesta .= "\n\n" . implode("\n", $fuentes);
}
}
function obtener_respuesta($prompt) {
$apiKey = trim((string) legacy_config('openai.api_key', ''));
$model = legacy_config('openai.model', 'gpt-4o-mini');
$endpoint = legacy_config('openai.endpoint', 'https://api.openai.com/v1/chat/completions');
if ($apiKey === '' || strpos($apiKey, 'CHANGE_ME_') === 0) {
return "⚠️ Configura openai.api_key en config/local.php.";
}
$ch = curl_init($endpoint);
$data = array(
'model' => $model,
'messages' => array(
array('role' => 'system', 'content' => 'Eres un redactor SEO experto en e-commerce.'),
array('role' => 'user', 'content' => $prompt)
),
'temperature' => 0.6,
'max_tokens' => 1200
);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Content-Type: application/json',
'Authorization: Bearer ' . $apiKey
));
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_TIMEOUT, 120);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
$result = curl_exec($ch);
if (curl_errno($ch)) {
return "⚠️ Error de conexión: " . curl_error($ch);
}
$http_status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($http_status !== 200) {
return "⚠️ Error HTTP $http_status — el servidor no respondió correctamente.";
}
$response = json_decode($result, true);
if (isset($response['choices'][0]['message']['content'])) {
return $response['choices'][0]['message']['content'];
} else {
return "⚠️ No se pudo generar respuesta. Detalle:\n" . json_encode($response, JSON_PRETTY_PRINT);
}
}
?>
<style>
.loader-container {
display: none;
justify-content: center;
align-items: center;
flex-direction: column;
margin-top: 30px;
}
.loader {
border: 5px solid #f3f3f3;
border-top: 5px solid #0074D9;
border-radius: 50%;
width: 50px;
height: 50px;
animation: spin 1s linear infinite;
}
@keyframes spin { 0% { transform: rotate(0deg);} 100% { transform: rotate(360deg);} }
.loading-text {
margin-top: 10px;
font-weight: bold;
font-size: 1.1rem;
color: #333;
animation: fadeText 1.5s infinite;
}
@keyframes fadeText {
0%,100% {opacity: 0.2;} 50% {opacity: 1;}
}
</style>
<div class="container">
<form method="POST" onsubmit="showLoader()">
<div class="row" style="margin-top: 20px">
<div class="ten columns">
<input class="u-full-width" type="text" name="prompt" id="prompt"
placeholder="Ingresa el nombre del producto"
value="<?php echo htmlspecialchars($producto); ?>">
</div>
<div class="two columns">
<button class="button button-primary u-full-width" type="submit">Generar</button>
</div>
</div>
<div class="row" style="margin-top:10px;">
<label>
<input type="checkbox" name="formato" value="formato" <?php echo ($formato==='formato')?'checked':''; ?>>
<span>Con formato para SEO</span>
</label>
</div>
</form>
<div id="loader" class="loader-container">
<div class="loader"></div>
<div class="loading-text">Generando contenido...</div>
</div>
<?php if ($autoGenerar && !isset($respuesta)) { ?>
<script>document.addEventListener("DOMContentLoaded",()=>{document.getElementById("loader").style.display="flex";});</script>
<?php } ?>
<?php if (isset($respuesta)) { ?>
<script>document.addEventListener("DOMContentLoaded",()=>{document.getElementById("loader").style.display="none";});</script>
<div class="row" style="margin-top:20px;">
<div class="twelve columns">
<pre id="texto-copiar" style="background:#f9f9f9;padding:15px;border-radius:6px;white-space:pre-wrap;font-size:1rem;"><?php echo htmlspecialchars($respuesta); ?></pre>
</div>
</div>
<div class="row" style="margin-top:10px;">
<div class="two columns">
<button class="button button-primary u-full-width" onclick="refreshPage()">
<i class="fa fa-refresh fa-lg"></i>
</button>
</div>
<div class="ten columns">
<button class="u-full-width" onclick="copiarTexto()">Copiar texto</button>
</div>
</div>
<?php } ?>
</div>
<script>
function showLoader(){document.getElementById("loader").style.display="flex";}
function copiarTexto(){
var e=document.getElementById("texto-copiar");
var t=e.innerText;
navigator.clipboard.writeText(t);
e.style.backgroundColor="#ffffa0";
setTimeout(()=>{e.style.backgroundColor="";alert("Texto copiado al portapapeles");},500);
}
function refreshPage(){location.reload();}
</script>
<?php include ('./inc/footer.php'); ?>