Guía Técnica de Implementación

Integración de un Asistente de IA (Generar, Editar y Guardar) en un Stack PHP/MySQL/JS

Fase 1: Modificaciones al Frontend (HTML)

Paso 1.1: Añadir el Botón de Activación de la IA
Propósito

Insertar un botón en la interfaz que iniciará todo el proceso. Este botón debe contener toda la información necesaria para las operaciones posteriores mediante atributos data-*.

Implementación

En el archivo carga_temas_subtemas_ver_ia.php, dentro del bucle while que genera las tarjetas, se modifica el `div.card-footer` de la tarjeta "Descripción General" para incluir el siguiente botón:

<!-- Botón que inicia el flujo de IA. Contiene toda la metadata necesaria. -->
<button 
    class="btn btn-outline-info btn-sm btn-ia-desc" 
    data-target-id="desc-<?= $id_counter ?>"
    data-record-id="<?= $tema['id'] ?>" 
    data-column-name="descripcion">
    IA
</button>
Análisis de Atributos
  • class="btn-ia-desc": Un selector CSS único para que JavaScript pueda identificar este botón específico.
  • data-target-id: Apunta al ID del elemento <p> que contiene el texto fuente (de la columna desc_todas).
  • data-record-id: Almacena la clave primaria (id) del registro en la tabla temas_curso.
  • data-column-name: Especifica la columna de destino para la operación de guardado (descripcion).
Paso 1.2: Añadir el Contenedor para la Respuesta
Propósito

Crear un marcador de posición (placeholder) en el DOM donde JavaScript inyectará dinámicamente el formulario de edición (<textarea> y botón "Grabar").

Implementación

Justo después del cierre de la tarjeta (</div><!-- FIN DE LA TARJETA -->), se añade el siguiente div:

<!-- Contenedor vacío que será poblado por JavaScript con el formulario de edición. -->
<div id="response-container-desc-<?= $id_counter ?>" class="ia-response-container mt-3"></div>

Fase 2: Creación de Scripts de Backend (PHP)

Paso 2.1: Script de Análisis con IA (avi/chat_ia_desc.php)
Propósito

Crear un "agente" de IA. Este script recibe el texto fuente, se comunica con la API de Google Gemini para obtener una sugerencia de mejora y devuelve el resultado al frontend.

Implementación
<?php
ini_set('display_errors', 1);
error_reporting(E_ALL);

header('Content-Type: application/json');
include("../include/conecta_mysql_otec.php");

define('GOOGLE_AI_API_KEY', 'TU_API_KEY_AQUI');
define('GOOGLE_AI_API_URL', 'https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=' . GOOGLE_AI_API_KEY);

function log_message($message) { /* ... función de log ... */ }

log_message("-> INICIO EJECUCIÓN: chat_ia_desc.php (Conexión a Gemini)");

$response = [ 'success' => false, 'data' => null, 'message' => 'Error' ];

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $post_data = file_get_contents('php://input');
    $request = json_decode($post_data, true);

    if (isset($request['text']) && !empty($request['text'])) {
        $texto_a_analizar = $request['text'];
        log_message("Texto recibido para analizar: " . substr($texto_a_analizar, 0, 80) . "...");

        $system_prompt = "Eres un asistente experto en diseño instruccional...";
        
        $payload = [ /* ... construcción del payload para Gemini ... */ ];

        log_message("Llamando a la API de Gemini...");
        $ch = curl_init(GOOGLE_AI_API_URL);
        curl_setopt_array($ch, [ /* ... opciones de cURL ... */ ]);
        $api_response_raw = curl_exec($ch);
        $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        log_message("Respuesta de la API recibida. HTTP Code: " . $http_code);

        if ($http_code == 200) {
            $api_response = json_decode($api_response_raw, true);
            if (isset($api_response['candidates'][0]['content']['parts'][0]['text'])) {
                $response['success'] = true;
                $response['data'] = nl2br(htmlspecialchars(trim($api_response['candidates'][0]['content']['parts'][0]['text'])));
                $response['message'] = 'Análisis de IA completado.';
            } else { /* ... manejo de error de API (ej. safety) ... */ }
        } else { /* ... manejo de error de conexión cURL ... */ }
    }
}
echo json_encode($response);
exit;
?>
Paso 2.2: Script de Guardado en Base de Datos (guardar_desc.php)
Propósito

Gestionar la persistencia de los datos. Este script recibe el texto final del frontend y lo actualiza en la base de datos de forma segura. Es un script dedicado y no genérico.

Implementación y Medidas de Seguridad
<?php
ini_set('display_errors', 1);
error_reporting(E_ALL);

header('Content-Type: application/json');
include("include/conecta_mysql_otec.php");

function log_message($message) { /* ... función de log ... */ }

log_message("-> INICIO GUARDADO: guardar_desc.php");
$response = [ 'success' => false, 'message' => 'Solicitud no válida.' ];

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $post_data = file_get_contents('php://input');
    $request = json_decode($post_data, true);

    if (isset($request['record_id']) && isset($request['text_content'])) {
        $record_id = filter_var($request['record_id'], FILTER_VALIDATE_INT);
        $text_content = $request['text_content'];
        
        // Medida de Seguridad 1: El nombre de la columna está escrito directamente (hardcoded).
        // No se puede manipular desde el frontend.
        $column_name = 'descripcion';

        if ($record_id) {
            // Medida de Seguridad 2: Se utiliza una consulta preparada.
            $sql = "UPDATE temas_curso SET {$column_name} = ? WHERE id = ?";
            if ($stmt = $mysqli->prepare($sql)) {
                $stmt->bind_param("si", $text_content, $record_id);
                if ($stmt->execute()) { /* ... manejo de éxito ... */ }
                $stmt->close();
            } else { /* ... manejo de error de preparación ... */ }
        }
    }
}
echo json_encode($response);
exit;
?>

Fase 3: Implementación de la Lógica (JavaScript)

Paso 3.1: Lógica del Botón "IA" (Generar y Mostrar)
Propósito

Manejar el evento de clic en el botón "IA", llamar al agente de IA y renderizar el formulario de edición con la respuesta.

document.addEventListener('DOMContentLoaded', function() {
    // ... otros listeners ...

    const botonIaDesc = document.querySelector('.btn-ia-desc');
    if (botonIaDesc) {
        botonIaDesc.addEventListener('click', function(event) {
            // ... Recolectar datos de los atributos data-* ...
            // ... Mostrar mensaje de "Cargando..." ...
            
            fetch('avi/chat_ia_desc.php', { /* ... opciones de fetch ... */ })
            .then(response => response.json())
            .then(data => {
                if (data.success) {
                    // Lógica para crear dinámicamente el HTML del formulario
                    const formHtml = `
                        <div class="border p-3 rounded bg-light">
                            <label for="ia-textarea-...">Sugerencia de la IA (editable):</label>
                            <textarea id="ia-textarea-..." class="form-control">${data.data}</textarea>
                            <button class="btn btn-primary btn-sm btn-save-ia" ...>Grabar Cambios</button>
                            <div id="save-status-..."></div>
                        </div>`;
                    responseContainer.innerHTML = formHtml;
                }
            });
        });
    }
});
Paso 3.2: Lógica del Botón "Grabar" (Delegación de Eventos)
Propósito

Manejar el clic en el botón "Grabar", que fue creado dinámicamente. Para esto, es imprescindible usar la técnica de **delegación de eventos**.

Implementación

Se añade un listener al contenedor principal (un elemento estático), que intercepta todos los clics y actúa solo si el objetivo es un botón de guardado.

// Dentro de document.addEventListener('DOMContentLoaded', function() { ... });

const mainContainer = document.querySelector('main.container');
if (mainContainer) {
    mainContainer.addEventListener('click', function(event) {
        // Se verifica si el objetivo del clic tiene la clase '.btn-save-ia'
        if (event.target.classList.contains('btn-save-ia')) {
            event.preventDefault();
            const saveButton = event.target;
            saveButton.disabled = true; // Prevenir doble clic

            // ... Recolectar datos del botón y del textarea ...
            // ... Mostrar mensaje "Guardando..." ...
            
            fetch('guardar_desc.php', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ /* ... datos a enviar ... */ })
            })
            .then(response => response.json())
            .then(data => {
                // ... Mostrar mensaje de éxito o error del backend ...
            })
            .catch(error => {
                // ... Manejar errores de conexión ...
            })
            .finally(() => {
                // Opcionalmente, reactivar el botón
                saveButton.disabled = false;
            });
        }
    });
}