<?php
// index.php - Single file: copiar datos entre tablas MySQL.

// -------------------- CONFIGURACIÓN --------------------
// ¡ATENCIÓN! REEMPLAZA ESTAS CREDENCIALES CON LAS TUYAS.
$HOST = "localhost";
$USER = "root";
$PASS = "33comRxXMysql"; 
// ----------------------------------------------------

// Manejo de errores básico
error_reporting(E_ALL); 
ini_set('display_errors', 1);

// Conexión global al servidor MySQL
$conn = new mysqli($HOST, $USER, $PASS);
$connection_error = null;
if ($conn->connect_error) {
    $connection_error = "Error de conexión al servidor MySQL: " . $conn->connect_error;
} else {
    $conn->set_charset("utf8mb4");
}

// -------------------- Funciones de Seguridad y Utilidad --------------------

/**
 * Escapa y envuelve un nombre de DB, tabla o columna en backticks (``).
 * @param string $s Nombre a escapar.
 * @return string|false Nombre escapado o false si es inválido.
 */
function quote_name($s) {
    if (!preg_match('/^[A-Za-z0-9_]+$/', $s)) {
        return false; 
    }
    return "`" . $s . "`";
}

// -------------------- backend AJAX --------------------
if (isset($_POST['action'])) {
    header('Content-Type: application/json; charset=utf-8');

    if ($connection_error) {
        echo json_encode(['ok'=>false, 'error'=>$connection_error]);
        exit;
    }

    $action = $_POST['action'];

    if ($action === 'get_databases') {
        $res = $conn->query("SHOW DATABASES");
        $dbs = [];
        $ignored_dbs = ['information_schema', 'mysql', 'performance_schema', 'sys'];
        while ($r = $res->fetch_assoc()) {
            if (!in_array($r['Database'], $ignored_dbs)) {
                $dbs[] = $r['Database'];
            }
        }
        echo json_encode(['ok'=>true,'databases'=>$dbs]); exit;
    }

    if ($action === 'get_tables') {
        $db = $_POST['db'] ?? '';
        $q_db = quote_name($db);
        
        if (!$q_db) { echo json_encode(['ok'=>false,'error'=>'DB inválida']); exit; }
        
        $res = $conn->query("SHOW TABLES FROM $q_db");
        
        if ($res === false) { echo json_encode(['ok'=>false,'error'=>'No se pudo acceder a la DB o error: '.$conn->error]); exit; }
        
        $tables = [];
        while ($r = $res->fetch_array()) $tables[] = $r[0];
        echo json_encode(['ok'=>true,'tables'=>$tables]); exit;
    }

    if ($action === 'get_columns') {
        $db = $_POST['db'] ?? '';
        $table = $_POST['table'] ?? '';
        $q_db = quote_name($db);
        $q_table = quote_name($table);
        
        if (!$q_db || !$q_table) { echo json_encode(['ok'=>false,'error'=>'DB o tabla inválida']); exit; }

        $sql = "SHOW FULL COLUMNS FROM $q_table FROM $q_db";
        $res = $conn->query($sql);
        
        if ($res === false) { echo json_encode(['ok'=>false,'error'=>$conn->error]); exit; }

        $cols = [];
        while ($r = $res->fetch_assoc()) {
            $cols[] = ['Field'=>$r['Field'], 'Type'=>$r['Type'], 'Null'=>$r['Null']];
        }
        echo json_encode(['ok'=>true,'columns'=>$cols]); exit;
    }

    // Lógica para EDITAR TIPO DE COLUMNA DESTINO
    if ($action === 'update_column_type') {
        $db = $_POST['db'];
        $table = $_POST['table'];
        $column = $_POST['column'];
        $newType = $_POST['newType']; 

        $q_db = quote_name($db);
        $q_table = quote_name($table);
        $q_column = quote_name($column);

        if (!$q_db || !$q_table || !$q_column) {
            echo json_encode(['ok'=>false,'error'=>'Nombres de DB/Tabla/Columna inválidos.']); exit;
        }
        
        if (strpos($newType, ';') !== false || strpos($newType, '`') !== false || strpos($newType, '--') !== false) {
             echo json_encode(['ok'=>false,'error'=>'Tipo de dato inválido o peligroso.']); exit;
        }

        if (!$conn->select_db($db)) {
            echo json_encode(['ok'=>false,'error'=>'No se pudo seleccionar DB: ' . $conn->error]); exit;
        }

        $sql = "ALTER TABLE {$q_table} MODIFY {$q_column} $newType";

        $ok = $conn->query($sql);

        echo json_encode([
            'ok' => $ok ? true : false,
            'sql' => $sql,
            'error' => $ok ? null : $conn->error
        ]);
        exit;
    }

    // COPIA DE DATOS (Incluye TRUNCATE opcional)
    if ($action === 'execute_copy') {
        $src_db = $_POST['src_db'] ?? '';
        $src_table = $_POST['src_table'] ?? '';
        $dst_db = $_POST['dst_db'] ?? '';
        $dst_table = $_POST['dst_table'] ?? '';
        $mappings_json = $_POST['mappings'] ?? '';
        $limit = isset($_POST['limit']) && is_numeric($_POST['limit']) ? intval($_POST['limit']) : null;
        $truncate_dst = filter_var($_POST['truncate_dst'] ?? false, FILTER_VALIDATE_BOOLEAN); 

        $q_src_db = quote_name($src_db);
        $q_src_table = quote_name($src_table);
        $q_dst_db = quote_name($dst_db);
        $q_dst_table = quote_name($dst_table);

        if (!$q_src_db || !$q_src_table || !$q_dst_db || !$q_dst_table) {
            echo json_encode(['ok'=>false,'error'=>'Nombres de DB/Tabla inválidos.']); exit;
        }

        $maps = json_decode($mappings_json, true);
        if (!is_array($maps) || count($maps)==0) {
            echo json_encode(['ok'=>false,'error'=>'Mapeos inválidos']); exit;
        }

        $srcCols = []; 
        $dstCols = [];
        foreach ($maps as $m) {
            $q_src = quote_name($m['src']);
            $q_dst = quote_name($m['dst']);
            if (!$q_src || !$q_dst) {
                 echo json_encode(['ok'=>false,'error'=>'Columna origen o destino en el mapeo es inválida.']); exit;
            }
            $srcCols[] = $q_src;
            $dstCols[] = $q_dst;
        }

        $dstColsList = implode(',', $dstCols);
        $srcColsList = implode(',', $srcCols);
        
        $insert_sql = "INSERT INTO {$q_dst_db}.{$q_dst_table} ({$dstColsList}) SELECT {$srcColsList} FROM {$q_src_db}.{$q_src_table}";
        if ($limit) $insert_sql .= " LIMIT $limit";
        
        $truncate_sql = "TRUNCATE TABLE {$q_dst_db}.{$q_dst_table}";

        try {
            $conn->begin_transaction();
            
            // 1. LIMPIAR TABLA si está marcado
            if ($truncate_dst) {
                $truncate_result = $conn->query($truncate_sql);
                if ($truncate_result === false) {
                    $err = $conn->error;
                    $conn->rollback();
                    echo json_encode(['ok'=>false,'error'=>'Error SQL al truncar (limpiar): '.$err,'sql'=>$truncate_sql]); exit;
                }
            }
            
            // 2. INSERTAR DATOS
            $result = $conn->query($insert_sql);

            if ($result === false) {
                $err = $conn->error;
                $conn->rollback();
                echo json_encode(['ok'=>false,'error'=>'Error SQL al insertar: '.$err,'sql'=>$insert_sql]); exit;
            }

            $affected = $conn->affected_rows;
            $conn->commit();

            $message_prefix = $truncate_dst ? "Tabla destino limpiada y " : "";
            echo json_encode(['ok'=>true,'message'=>"{$message_prefix}copiadas $affected filas",'affected'=>$affected,'sql'=>$insert_sql]); exit;

        } catch (Exception $e) {
            $conn->rollback();
            echo json_encode(['ok'=>false,'error'=>$e->getMessage(),'sql'=>$insert_sql]); exit;
        }
    }

    echo json_encode(['ok'=>false,'error'=>'Acción desconocida']); exit;
}
// -------------------- frontend HTML --------------------
?>
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<title>Copiar datos entre tablas MySQL</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>

<style>
.field-item{cursor:pointer}
.field-item.active{background:#e9f5ff}
.mapping-line{display:flex;justify-content:space-between;align-items:center;padding:.35rem .5rem;border:1px solid #eee;border-radius:6px;margin-bottom:.35rem}
</style>
</head>
<body class="bg-light p-4">
<div class="container">

<h3 class="mb-4">📋 Copiar datos entre Bases de Datos (Origen → Destino)</h3>

<?php if ($connection_error): ?>
    <div class="alert alert-danger">
        <strong>🚨 Error de Conexión:</strong> <?php echo $connection_error; ?>
        <p class="small mt-2 mb-0">Revisa las credenciales `$HOST`, `$USER`, `$PASS` al inicio del archivo.</p>
    </div>
<?php else: ?>

<div class="row g-3">

    <div class="col-md-6">
      <div class="card p-3">
        <h5 class="text-primary">1. Origen</h5>
        <div class="mb-2">
          <label class="form-label">Base de datos</label>
          <select id="src_db" class="form-select"></select>
        </div>
        <div class="mb-2">
          <label class="form-label">Tabla</label>
          <select id="src_table" class="form-select" disabled>
            <option value="">-- Selecciona DB --</option>
          </select>
        </div>
        <h6 class="mt-3">Estructura Origen</h6>
        <div id="src_columns" class="mt-2" style="min-height:120px;background:white;padding:10px;border:1px solid #ddd;border-radius:6px"></div>
      </div>
    </div>

    <div class="col-md-6">
      <div class="card p-3">
        <h5 class="text-success">2. Destino</h5>
        <div class="mb-2">
          <label class="form-label">Base de datos</label>
          <select id="dst_db" class="form-select"></select>
        </div>
        <div class="mb-2">
          <label class="form-label">Tabla</label>
          <select id="dst_table" class="form-select" disabled>
            <option value="">-- Selecciona DB --</option>
          </select>
        </div>

        <h6 class="mt-3">Estructura Destino</h6>
        <div id="dst_columns" class="mt-2"
              style="min-height:120px;background:white;padding:10px;border:1px solid #ddd;border-radius:6px"></div>
      </div>
    </div>

</div>

<div class="mt-4 card p-3">
    <h5>3. Mapeos Manuales</h5>
    <p class="text-muted small">Haz clic en un campo **origen** y luego en un campo **destino** para crear una relación.</p>
    <div id="mappings_list" class="mb-3">
        <p class="text-muted small">Crea mapeos para verlos aquí.</p>
    </div>

    <div class="row g-2 mb-2 align-items-end">
      <div class="col-md-3">
        <label class="form-label small">Límite de filas</label>
        <input id="limit" class="form-control" placeholder="Ej: 1000 (Opcional)"/>
      </div>
      <div class="col-md-4">
        <div class="form-check">
            <input class="form-check-input" type="checkbox" value="" id="truncate_dst">
            <label class="form-check-label text-danger" for="truncate_dst">
                **LIMPIAR** tabla destino (TRUNCATE)
            </label>
        </div>
      </div>
      <div class="col-md-5 text-end">
        <button id="btn_generate" class="btn btn-outline-secondary">Generar SQL</button>
        <button id="btn_execute" class="btn btn-primary">4. Ejecutar copia</button>
      </div>
    </div>

    <div id="status_area" class="mt-3"></div>
    <h6 class="mt-3">SQL generado</h6>
    <pre id="sql_area" class="bg-dark text-white p-2" style="white-space:pre-wrap; font-size: 0.85em;"></pre>
</div>

<?php endif; ?>
</div>

<div class="modal fade" id="editTypeModal" tabindex="-1">
  <div class="modal-dialog">
    <div class="modal-content">

      <div class="modal-header bg-dark text-white">
        <h5 class="modal-title">✏️ Editar tipo de columna Destino</h5>
        <button class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
      </div>

      <div class="modal-body">
        <input type="hidden" id="edit_db">
        <input type="hidden" id="edit_table">
        <input type="hidden" id="edit_column">

        <label>Columna a modificar:</label>
        <p id="edit_column_name" class="fw-bold fs-5 text-primary"></p> 

        <label for="edit_newType">Nuevo tipo MySQL:</label>
        <input id="edit_newType" class="form-control" placeholder="Ej: VARCHAR(200) NOT NULL">
        <small class="text-danger">**Cuidado:** Esta acción modifica la estructura de la tabla Destino.</small>
      </div>

      <div class="modal-footer">
        <button class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
        <button class="btn btn-success" id="btn_saveType">Guardar y Modificar</button>
      </div>

    </div>
  </div>
</div>

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>

<script>
$(function(){
    
    // =================================================================
    // 🗄️ APLICACIÓN CENTRAL (Objeto App)
    // =================================================================
    const App = {
        selectedSrc: null,
        mappings: [],
        
        init: function() {
            if (!$('#status_area').parent().hasClass('alert-danger')) {
                this.loadDatabases();
                this.bindEvents();
            }
        },
        
        showStatus: function(msg, type='info') { 
            $('#status_area').html(`<div class="alert alert-${type}">${msg}</div>`); 
        },
        
        clearStatus: function() { 
            $('#status_area').html(''); 
        },
        
        // ------------------ Peticiones AJAX (Modularizadas) ------------------
        ajaxPost: function(data, successCallback) {
            $.post('', data, function(res) {
                if (res.ok) {
                    successCallback(res);
                } else {
                    App.showStatus(`Error en la operación: ${res.error}`, 'danger');
                }
            }, 'json').fail(function(jqXHR, textStatus, errorThrown) {
                 App.showStatus(`Error de red o servidor: ${textStatus} ${errorThrown}`, 'danger');
            });
        },
        
        loadDatabases: function() {
            this.ajaxPost({ action: 'get_databases' }, function(res) {
                $('#src_db, #dst_db').html('<option value="">-- seleccionar --</option>');
                res.databases.forEach(d=> { $('#src_db, #dst_db').append(`<option value="${d}">${d}</option>`); });
            });
        },
        
        loadTables: function(dbId) {
            const db = $(dbId).val();
            const tableId = dbId === '#src_db' ? '#src_table' : '#dst_table';
            const columnsId = dbId === '#src_db' ? '#src_columns' : '#dst_columns';
            
            $(tableId).prop('disabled', true).html('<option value="">Cargando...</option>');
            $(columnsId).html('');
            this.mappings = []; 
            this.renderMappings();
            
            if (!db) {
                $(tableId).html('<option value="">-- Selecciona DB --</option>');
                return;
            }

            this.ajaxPost({ action: 'get_tables', db: db }, function(res) {
                $(tableId).prop('disabled', false).html('<option value="">-- seleccionar --</option>');
                res.tables.forEach(t => $(tableId).append(`<option>${t}</option>`));
            });
        },
        
        loadColumns: function(dbId, tableId) {
            const db = $(dbId).val();
            const table = $(tableId).val();
            const containerId = dbId === '#src_db' ? '#src_columns' : '#dst_columns';
            const isDest = dbId === '#dst_db';
            
            $(containerId).html('Cargando...');
            this.mappings = []; 
            this.renderMappings();

            if (!db || !table) return;

            this.ajaxPost({ action: 'get_columns', db, table }, function(res) {
                App.renderColumns(containerId, res.columns, isDest);
            });
        },
        
        // ------------------ Renderización ------------------
        renderColumns: function(target, cols, isDest = false) {
            const container = $(target);
            container.empty();

            cols.forEach(c => {
                let html = `
                <div class="field-item p-2 mb-1 border rounded d-flex justify-content-between align-items-center"
                    data-field="${c.Field}"
                    data-type="${c.Type}">
                    <span>
                        ${c.Field} 
                        <small class="text-secondary">(${c.Type} ${c.Null === 'NO' ? 'NOT NULL' : 'NULL'})</small>
                    </span>
                `;

                if (isDest) {
                    html += `
                    <button class="btn btn-sm btn-warning btnEditType" type="button"
                        data-bs-toggle="modal"
                        data-bs-target="#editTypeModal"
                        data-column="${c.Field}"
                        data-type="${c.Type}">
                        <small>Editar</small>
                    </button>
                    `;
                }
                html += `</div>`;
                container.append(html);
            });
        },
        
        renderMappings: function() {
            const box = $('#mappings_list'); box.empty();
            if (this.mappings.length === 0) {
                 box.html('<p class="text-muted small">Crea mapeos para verlos aquí.</p>');
            } else {
                this.mappings.forEach((m,i)=>{
                    box.append(`
                        <div class="mapping-line">
                            <div><strong>${m.src}</strong> ➜ <strong>${m.dst}</strong></div>
                            <div><span class="remove btn btn-sm btn-outline-danger" data-idx="${i}">Eliminar</span></div>
                        </div>
                    `);
                });
            }
            this.generateSQL();
        },
        
        // ------------------ Lógica de Mapeo y Eventos ------------------
        bindEvents: function() {
            $('#src_db').on('change', () => this.loadTables('#src_db'));
            $('#dst_db').on('change', () => this.loadTables('#dst_db'));
            
            $('#src_table').on('change', () => this.loadColumns('#src_db', '#src_table'));
            $('#dst_table').on('change', () => this.loadColumns('#dst_db', '#dst_table'));
            
            // Clic en Origen
            $(document).on('click', '#src_columns .field-item', function(){
                $('#src_columns .field-item').removeClass('active');
                $(this).addClass('active');
                App.selectedSrc = $(this).data('field');
                App.showStatus(`Origen seleccionado: <strong>${App.selectedSrc}</strong>`, 'info');
            });
            
            // Clic en Destino
            $(document).on('click', '#dst_columns .field-item', function(){
                if (!App.selectedSrc) { App.showStatus('Selecciona columna origen', 'warning'); return; }
                const dst = $(this).data('field');

                if (App.mappings.some(m => m.dst === dst)) {
                    App.showStatus('Destino ya mapeado', 'warning');
                    return;
                }

                if (App.mappings.some(m => m.src === App.selectedSrc)) {
                    App.showStatus(`La columna origen ${App.selectedSrc} ya está mapeada.`, 'warning');
                    return;
                }

                App.mappings.push({ src: App.selectedSrc, dst });
                App.selectedSrc = null;
                $('#src_columns .field-item').removeClass('active');

                App.renderMappings();
                App.clearStatus();
            });

            // Eliminar Mapeo
            $(document).on('click', '.mapping-line .remove', function(){
                App.mappings.splice($(this).data('idx'), 1);
                App.renderMappings();
            });
            
            // 🚨 CORRECCIÓN AQUÍ: Asignación de datos al abrir el modal 🚨
            $(document).on("click", ".btnEditType", function(){
                const column = $(this).data("column");
                const type = $(this).data("type");
                
                // 1. Asignar los valores a los inputs del modal
                $("#edit_db").val($("#dst_db").val());
                $("#edit_table").val($("#dst_table").val());
                $("#edit_column").val(column);
                $("#edit_newType").val(type);
                
                // 2. Mostrar el nombre de la columna en el encabezado
                $("#edit_column_name").text(column); 
            });
            
            // Guardar Tipo Nuevo
            $("#btn_saveType").click(function(){
                App.ajaxPost({
                    action: 'update_column_type',
                    db: $("#edit_db").val(),
                    table: $("#edit_table").val(),
                    column: $("#edit_column").val(),
                    newType: $("#edit_newType").val()
                }, function(res) {
                    App.showStatus("Modificación aplicada correctamente.", "success");
                    $('#dst_table').trigger('change'); 
                    $("#editTypeModal").modal("hide");
                });
            });

            // Generar y Ejecutar SQL
            $('#btn_generate').click(() => this.generateSQL());
            $('#btn_execute').click(() => this.executeCopy());
        },
        
        generateSQL: function() {
            if (this.mappings.length===0){ $('#sql_area').text(''); return ''; }

            const src_db = $('#src_db').val(),
                  src_table = $('#src_table').val(),
                  dst_db = $('#dst_db').val(),
                  dst_table = $('#dst_table').val();

            if (!src_db || !src_table || !dst_db || !dst_table) return '';

            const dstCols = this.mappings.map(m=>" `"+m.dst+"` ").join(', ');
            const srcCols = this.mappings.map(m=>" `"+m.src+"` ").join(', ');
            const truncate_dst = $('#truncate_dst').is(':checked'); 
            
            let sql_full = '';
            
            if (truncate_dst) {
                sql_full += `TRUNCATE TABLE \`${dst_db}\`.\`${dst_table}\`;\n\n`;
            }

            let sql = `INSERT INTO \`${dst_db}\`.\`${dst_table}\` (${dstCols})
SELECT ${srcCols}
FROM \`${src_db}\`.\`${src_table}\``;

            const limit = $('#limit').val().trim();
            if (limit) sql += `\nLIMIT ${limit}`;
            sql += ';';
            
            sql_full += sql;

            $('#sql_area').text(sql_full);
            return sql_full;
        },
        
        executeCopy: function() {
            const sql = this.generateSQL();
            if (!sql){ this.showStatus('No hay SQL para ejecutar. Asegúrate de seleccionar las 4 tablas y crear mapeos.','warning'); return; }
            if (!confirm('⚠️ ¿Estás seguro de que quieres ejecutar la copia de datos? Esta acción es irreversible, especialmente si seleccionaste LIMPIAR (TRUNCATE).')) return;

            this.ajaxPost({
                action:'execute_copy',
                src_db:$('#src_db').val(),
                src_table:$('#src_table').val(),
                dst_db:$('#dst_db').val(),
                dst_table:$('#dst_table').val(),
                mappings:JSON.stringify(this.mappings),
                limit:$('#limit').val().trim(),
                truncate_dst:$('#truncate_dst').is(':checked') 
            }, function(res){
                App.showStatus(res.message, "success");
                const display_sql = res.sql ? (res.sql + "\n\n-- Filas afectadas: " + res.affected) : "SQL no devuelto.";
                $('#sql_area').text(display_sql); 
            });
        }
    };
    
    // Iniciar la aplicación
    App.init();
});
</script>

</body>
</html>