# -*- coding: utf-8 -*-
"""
TEITAN MASTER RESCUE ENGINE — ULTRA-SAFE V5 (PRESENCE LOCK EXCLUSION & RESUME)
Subsanación específica y focalizada para la reanudación segura:
1. EXCLUSIÓN ESTRICTAMENTE DELIMITADA DE ARCHIVOS .LOCK DE PRESENCIA:
   - Aplica ÚNICAMENTE a archivos que cumplan exactamente:
     dir == r"C:\\Users\\intel\\.gemini\\antigravity\\presence" y f.endswith(".lock")
   - Prohibido excluir otros .lock (ej. en Digital Optimus, bases de datos o laboratorios).
   - Se registran en `EXCLUSIONS_REPORT.json` con:
     ruta completa, motivo="VOLATILE_ANTIGRAVITY_PRESENCE_LOCK", timestamp y total.
2. REANUDACIÓN TRANSACCIONAL DESDE SQLITE EXISTENTE:
   - Conexión y verificación de integridad `PRAGMA integrity_check` sobre `stage_checkpoint.sqlite`.
   - Para los 147,609 archivos ya registrados con status 'VERIFIED':
     Se verifica la existencia y coincidencia física en Kingston; CERO relecturas a C:.
3. CÁLCULO DINÁMICO DE ESPACIO LIBRE NECESARIO:
   - No impone un umbral fijo de 135 GiB si gran parte de los datos ya están en el stage.
   - Calcula: bytes_pendientes_por_copiar + tamaño_estimado_zip + margen_seguridad (15 GiB).
   - Verifica dinámicamente que Kingston cuente con el espacio real necesario antes de operar.
4. DETECCIÓN ESTRICTA ANTE CUALQUIER OTRO ERROR:
   - Si ocurre cualquier otro error de lectura en un archivo no perteneciente a la regla de exclusión,
     aborta inmediatamente con exit code 1 y persiste `ERROR_REPORT.json`.
"""

import os
import sys
import time
import shutil
import zipfile
import hashlib
import json
import string
import ctypes
import subprocess
import uuid
import sqlite3

EXPECTED_KINGSTON_SERIAL_SUBSTR = "0026_B728_3F45_1265"
SAFETY_MARGIN_BYTES = 15 * 1024 * 1024 * 1024  # 15 GiB margen de seguridad mínimo

MANDATORY_ROOTS = [
    r"C:\xampp\htdocs\Digital Optimus",
    r"C:\Users\intel\AppData\Local\OptimusRuntime",
    r"C:\Users\intel\.gemini\antigravity",
    r"C:\xampp\mysql\data",
    r"C:\Users\intel\AR88_Sandbox"
]

MANDATORY_EXPLICIT_FILES = [
    r"C:\Users\intel\Downloads\Driver GT 710 Original\Digital_Optimus_Complete_Project.zip",
    r"C:\Users\intel\Downloads\DIGITAL_OPTIMUS_NORTE_MAESTRO.md.txt",
    r"C:\Users\intel\Downloads\LEEME_INSTRUCCIONES_MIGRACION.md",
    r"C:\Users\intel\Downloads\ORC_CIERRE_OPERATIVO_V1_1_0_2026-10-28.md",
    r"C:\Users\intel\Downloads\Digital_Optimus_Complete_Project.zip.sha256",
    r"C:\Users\intel\Desktop\AR72_CIRCUITO_AUTORREPARACION.txt",
    r"C:\Users\intel\Desktop\LICENSE"
]

PRESENCE_LOCK_DIR = os.path.normcase(os.path.normpath(r"C:\Users\intel\.gemini\antigravity\presence"))

def is_presence_lock_file(fpath):
    """Verifica si el archivo es estrictamente un .lock volátil en presence/ de antigravity."""
    norm_path = os.path.normcase(os.path.normpath(fpath))
    parent_dir = os.path.dirname(norm_path)
    filename = os.path.basename(norm_path)
    return parent_dir == PRESENCE_LOCK_DIR and filename.endswith(".lock")

class CheckpointDB:
    def __init__(self, db_path):
        self.db_path = db_path
        self._init_db()

    def _init_db(self):
        if os.path.exists(self.db_path) and os.path.getsize(self.db_path) > 0:
            conn = None
            try:
                conn = sqlite3.connect(self.db_path)
                cursor = conn.cursor()
                cursor.execute("PRAGMA integrity_check;")
                res = cursor.fetchone()
                if not res or res[0] != "ok":
                    raise RuntimeError(f"BLOQUEO: Base de datos de checkpoint corrupta ({res}).")
            except Exception as e:
                raise RuntimeError(f"BLOQUEO: Error crítico validando integridad de checkpoint DB: {e}")
            finally:
                if conn:
                    conn.close()

        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        cursor.execute("PRAGMA journal_mode = WAL;")
        cursor.execute("PRAGMA synchronous = NORMAL;")
        cursor.execute("""
            CREATE TABLE IF NOT EXISTS file_checkpoints (
                norm_source TEXT PRIMARY KEY,
                original_source TEXT NOT NULL,
                staged_path TEXT NOT NULL,
                bytes INTEGER NOT NULL,
                sha256 TEXT NOT NULL,
                status TEXT NOT NULL,
                timestamp REAL NOT NULL
            );
        """)
        conn.commit()
        conn.close()

    def get_entry(self, norm_source):
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        cursor.execute("SELECT bytes, sha256, status, staged_path FROM file_checkpoints WHERE norm_source = ?", (norm_source,))
        row = cursor.fetchone()
        conn.close()
        if row:
            return {"bytes": row[0], "sha256": row[1], "status": row[2], "staged_path": row[3]}
        return None

    def record_entry(self, norm_source, original_source, staged_path, byte_count, sha256_hash, status):
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        cursor.execute("""
            INSERT OR REPLACE INTO file_checkpoints 
            (norm_source, original_source, staged_path, bytes, sha256, status, timestamp)
            VALUES (?, ?, ?, ?, ?, ?, ?)
        """, (norm_source, original_source, staged_path, byte_count, sha256_hash, status, time.time()))
        conn.commit()
        conn.close()

    def get_total_staged_bytes(self):
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        cursor.execute("SELECT sum(bytes) FROM file_checkpoints WHERE status = 'VERIFIED'")
        row = cursor.fetchone()
        conn.close()
        return row[0] if row and row[0] else 0

def get_verified_target_volume():
    """Valida físicamente que la letra pertenezca al SSD Kingston con serial y etiqueta esperados."""
    kernel32 = ctypes.windll.kernel32
    volumeNameBuffer = ctypes.create_unicode_buffer(1024)
    fileSystemNameBuffer = ctypes.create_unicode_buffer(1024)
    serial_number = ctypes.c_ulong()
    max_component_length = ctypes.c_ulong()
    file_system_flags = ctypes.c_ulong()

    candidate_drive = None
    for letter in string.ascii_uppercase:
        drive = f"{letter}:\\"
        try:
            res = kernel32.GetVolumeInformationW(
                ctypes.c_wchar_p(drive),
                volumeNameBuffer,
                ctypes.sizeof(volumeNameBuffer),
                ctypes.byref(serial_number),
                ctypes.byref(max_component_length),
                ctypes.byref(file_system_flags),
                fileSystemNameBuffer,
                ctypes.sizeof(fileSystemNameBuffer)
            )
            if res and volumeNameBuffer.value == "TEITAN_RESCATE":
                candidate_drive = drive
                break
        except Exception:
            pass

    if not candidate_drive:
        return None, "No se encontro volumen con etiqueta TEITAN_RESCATE"

    drive_letter = candidate_drive[0]
    ps_cmd = f"Get-Partition -DriveLetter {drive_letter} | Get-Disk | Select-Object FriendlyName, SerialNumber, Number | ConvertTo-Json"
    try:
        proc = subprocess.run(["powershell", "-NoProfile", "-Command", ps_cmd], capture_output=True, text=True, timeout=10)
        if proc.returncode == 0 and proc.stdout.strip():
            disk_info = json.loads(proc.stdout)
            friendly = disk_info.get("FriendlyName", "")
            serial = disk_info.get("SerialNumber", "")
            if "KINGSTON" not in friendly or EXPECTED_KINGSTON_SERIAL_SUBSTR not in serial:
                return None, f"El volumen {candidate_drive} reside en {friendly} (S/N: {serial}), NO en el Kingston esperado."
        else:
            return None, f"No fue posible consultar la procedencia fisica del volumen {candidate_drive}."
    except Exception as e:
        return None, f"Error validando disco fisico destino: {e}"

    return candidate_drive, "OK"

def calculate_sha256(filepath):
    sha = hashlib.sha256()
    with open(filepath, "rb") as f:
        while chunk := f.read(4 * 1024 * 1024):
            sha.update(chunk)
    return sha.hexdigest().upper()

def atomic_copy_and_verify(src, dst, checkpoint_db):
    """
    Copia atómica ultra-segura con checkpointing SQLite y detección de truncamiento:
    - Comprueba checkpoint: si ya está registrado y verificado en Kingston, CERO lecturas a C:.
    - Detección de truncamiento: compara bytes leídos contra tamaño esperado antes y después.
    - Escritura en .atomic_tmp con fsync y doble verificación hash independiente.
    """
    norm_src = os.path.normcase(os.path.normpath(src))

    # 1. Comprobación de checkpoint incremental (Reanudación real sin tocar SSD C:)
    entry = checkpoint_db.get_entry(norm_src)
    if entry and entry.get("status") == "VERIFIED":
        expected_bytes = entry.get("bytes")
        expected_sha = entry.get("sha256")
        if os.path.exists(dst) and os.path.getsize(dst) == expected_bytes:
            dst_sha = calculate_sha256(dst)
            if dst_sha == expected_sha:
                return expected_bytes, dst_sha, "RESUMED_VERIFIED_NO_READ_SOURCE"

    # 2. Medición inicial del archivo origen en C:
    expected_size = os.path.getsize(src)

    os.makedirs(os.path.dirname(dst), exist_ok=True)
    temp_dst = f"{dst}.atomic_tmp_{os.getpid()}_{uuid.uuid4().hex}"
    if os.path.exists(temp_dst):
        os.remove(temp_dst)

    sha_src = hashlib.sha256()
    total_copied = 0
    with open(src, "rb") as fsrc, open(temp_dst, "wb") as fdst:
        while chunk := fsrc.read(4 * 1024 * 1024):
            sha_src.update(chunk)
            fdst.write(chunk)
            total_copied += len(chunk)
        fdst.flush()
        os.fsync(fdst.fileno())

    # 3. DETECCIÓN ESTRICTA DE COPIA TRUNCADA / DISCREPANCIA
    if total_copied != expected_size:
        if os.path.exists(temp_dst):
            os.remove(temp_dst)
        raise IOError(f"COPIA TRUNCADA: Bytes copiados ({total_copied}) != Tamaño esperado ({expected_size}) en {src}")

    post_read_size = os.path.getsize(src)
    if post_read_size != expected_size:
        if os.path.exists(temp_dst):
            os.remove(temp_dst)
        raise IOError(f"INCONSISTENCIA EN ORIGEN: Tamaño varió durante lectura ({expected_size} -> {post_read_size}) en {src}")

    source_hash = sha_src.hexdigest().upper()
    shutil.copystat(src, temp_dst)

    # 4. Comprobación independiente del archivo escrito en Kingston
    dst_written_hash = calculate_sha256(temp_dst)
    if dst_written_hash != source_hash:
        if os.path.exists(temp_dst):
            os.remove(temp_dst)
        raise IOError(f"DISCREPANCIA DE INTEGRIDAD: Hash origen {source_hash} != Hash escrito {dst_written_hash}")

    # 5. Preservación estricta de archivo previo en Kingston si existe y difiere
    if os.path.exists(dst):
        existing_sha = calculate_sha256(dst)
        if existing_sha != source_hash:
            unique_bak = f"{dst}.bak_{int(time.time())}_{uuid.uuid4().hex[:6]}"
            print(f"  [PRESERVACION] Archivo existente difiere. Preservando original en: {unique_bak}")
            os.rename(dst, unique_bak)
        else:
            os.remove(temp_dst)
            checkpoint_db.record_entry(norm_src, src, dst, total_copied, source_hash, "VERIFIED")
            return total_copied, source_hash, "EXISTING_IDENTICAL_PRESERVED"

    # 6. Promoción segura
    os.replace(temp_dst, dst)

    # 7. Actualizar checkpoint incremental en SQLite (O(1))
    checkpoint_db.record_entry(norm_src, src, dst, total_copied, source_hash, "VERIFIED")

    return total_copied, source_hash, "COPIED_AND_VERIFIED"

def safe_walk(root_path, scan_errors):
    """Exploración segura con captura explícita de directorios inaccesibles."""
    def on_walk_error(err):
        scan_errors.append(str(err))
    
    file_list = []
    for root, dirs, files in os.walk(root_path, onerror=on_walk_error):
        for f in files:
            file_list.append(os.path.join(root, f))
    return file_list

def build_prioritized_inventory():
    """Compila el inventario en orden de prioridad estricto, separando explícitamente exclusiones justificadas."""
    scan_errors = []

    for r in MANDATORY_ROOTS:
        if not os.path.exists(r):
            raise RuntimeError(f"RAIZ OBLIGATORIA AUSENTE: {r}")

    for f in MANDATORY_EXPLICIT_FILES:
        if not os.path.exists(f):
            raise RuntimeError(f"ARCHIVO OBLIGATORIO AUSENTE: {f}")

    # 1. Producción Digital Optimus
    p1_digital_optimus = safe_walk(r"C:\xampp\htdocs\Digital Optimus", scan_errors)

    # 2. Contratos y archivos obligatorios externos
    p2_critical_externals = list(MANDATORY_EXPLICIT_FILES)
    desktop_dir = r"C:\Users\intel\Desktop"
    downloads_dir = r"C:\Users\intel\Downloads"
    if os.path.exists(desktop_dir):
        for f in os.listdir(desktop_dir):
            if f.endswith((".zip", ".txt", ".md", ".json")):
                full = os.path.join(desktop_dir, f)
                if full not in p2_critical_externals:
                    p2_critical_externals.append(full)

    if os.path.exists(downloads_dir):
        for f in os.listdir(downloads_dir):
            if f.startswith("ORC") or f.startswith("AR") or f.startswith("TEITAN_AR"):
                full = os.path.join(downloads_dir, f)
                if os.path.isfile(full) and full not in p2_critical_externals:
                    p2_critical_externals.append(full)

    # 3. OptimusRuntime, Antigravity Brain y persistencias MySQL
    p3_runtime = safe_walk(r"C:\Users\intel\AppData\Local\OptimusRuntime", scan_errors)
    p3_antigravity_all = safe_walk(r"C:\Users\intel\.gemini\antigravity", scan_errors)
    p3_mysql = safe_walk(r"C:\xampp\mysql\data", scan_errors)

    # Filtrar EXCLUSIVAMENTE los presence/*.lock
    p3_antigravity = []
    justified_exclusions = []
    for f in p3_antigravity_all:
        if is_presence_lock_file(f):
            justified_exclusions.append({
                "path": f,
                "reason": "VOLATILE_ANTIGRAVITY_PRESENCE_LOCK",
                "timestamp": time.strftime("%Y-%m-%d %H:%M:%S")
            })
        else:
            p3_antigravity.append(f)

    # 4. Laboratorio AR88
    p4_ar88 = []
    ar88_raw = safe_walk(r"C:\Users\intel\AR88_Sandbox", scan_errors)
    for f in ar88_raw:
        if not f.endswith(".tmp_zip") and not (".atomic_tmp" in f):
            p4_ar88.append(f)

    if scan_errors:
        raise RuntimeError(f"DIRECTORIOS INACCESIBLES DETECTADOS DURANTE ESCANEO: {scan_errors}")

    seen = set()
    core_ordered = []
    for item in p1_digital_optimus + p2_critical_externals + p3_runtime + p3_antigravity + p3_mysql:
        norm = os.path.normcase(os.path.normpath(item))
        if norm not in seen:
            seen.add(norm)
            core_ordered.append(item)

    lab_ordered = []
    for item in p4_ar88:
        norm = os.path.normcase(os.path.normpath(item))
        if norm not in seen:
            seen.add(norm)
            lab_ordered.append(item)

    master_ordered = core_ordered + lab_ordered

    return {
        "core": core_ordered,
        "lab": lab_ordered,
        "master_all": master_ordered,
        "total_files": len(master_ordered),
        "digital_optimus_count": len(p1_digital_optimus),
        "justified_exclusions": justified_exclusions
    }

def stage_copy_group(file_list, stage_root, checkpoint_db, label):
    print(f"\n[{label}] Iniciando transferencia/verificacion hacia Kingston: {len(file_list)} archivos...")
    manifest = []
    errors = []
    total_bytes = 0
    start_time = time.time()
    count = 0

    for fpath in file_list:
        if not os.path.exists(fpath):
            errors.append({"path": fpath, "error": "Archivo no encontrado en origen"})
            break
        try:
            drive, rel = os.path.splitdrive(os.path.normpath(fpath))
            arcname = rel.lstrip("\\/")
            dst_path = os.path.join(stage_root, arcname)

            bytes_copied, file_sha, status = atomic_copy_and_verify(fpath, dst_path, checkpoint_db)
            total_bytes += bytes_copied
            count += 1
            manifest.append({
                "source": fpath,
                "arcname": arcname,
                "staged_path": dst_path,
                "bytes": bytes_copied,
                "sha256": file_sha,
                "status": status
            })

            if count % 2500 == 0 or bytes_copied > 100 * 1024 * 1024:
                elapsed = time.time() - start_time
                mb_s = (total_bytes / (1024*1024)) / elapsed if elapsed > 0 else 0
                print(f"  [{label}] Progreso: {count}/{len(file_list)} ({total_bytes / (1024**3):.2f} GiB, {mb_s:.1f} MB/s) [{status}]...")

        except Exception as e:
            print(f"\n[BLOQUEO CRITICO LECTURA C:] {fpath} -> {e}")
            errors.append({"path": fpath, "error": str(e)})
            break

    elapsed = time.time() - start_time
    print(f"[{label}] Transferencia finalizada: {count} archivos procesados ({total_bytes / (1024**3):.2f} GiB) en {elapsed:.1f}s.")
    return manifest, errors

def preserve_and_create_zip_from_stage(staged_items, target_zip, description):
    """Crea ZIP64 leyendo exclusivamente de Kingston con preservación de versiones previas."""
    if os.path.exists(target_zip):
        counter = 1
        ts = time.strftime("%Y%m%d_%H%M%S")
        backup_zip = f"{target_zip}.bak_{ts}_{counter:03d}"
        while os.path.exists(backup_zip):
            counter += 1
            backup_zip = f"{target_zip}.bak_{ts}_{counter:03d}"
        print(f"[PRESERVACION ESTRICTA] Archivo ZIP previo detectado. Preservando copia integra en:\n  -> {backup_zip}")
        os.rename(target_zip, backup_zip)

    temp_zip = f"{target_zip}.tmp_{os.getpid()}_{uuid.uuid4().hex}.zip"

    print(f"\n--- EMPAQUETANDO {description} (ZIP64 SIN CIFRADO) ---")
    print(f"Fuente: Kingston Stage")
    print(f"Destino Temporal: {temp_zip}")
    print(f"Destino Definitivo: {target_zip}")

    total_uncompressed = 0
    start_time = time.time()

    with zipfile.ZipFile(temp_zip, 'w', compression=zipfile.ZIP_DEFLATED, allowZip64=True, compresslevel=6) as zf:
        count = 0
        for item in staged_items:
            staged_file = item["staged_path"]
            arcname = item["arcname"]
            fsize = item["bytes"]
            zf.write(staged_file, arcname=arcname)
            total_uncompressed += fsize
            count += 1
            if count % 2500 == 0 or fsize > 100 * 1024 * 1024:
                elapsed = time.time() - start_time
                mb_s = (total_uncompressed / (1024*1024)) / elapsed if elapsed > 0 else 0
                print(f"  Progreso ZIP: {count}/{len(staged_items)} ({total_uncompressed / (1024**3):.2f} GiB, {mb_s:.1f} MB/s)...")

    print(f"Verificando integridad CRC interna en archivo temporal...")
    with zipfile.ZipFile(temp_zip, 'r') as zf:
        bad_file = zf.testzip()
        if bad_file is not None:
            if os.path.exists(temp_zip):
                os.remove(temp_zip)
            raise RuntimeError(f"ERROR INTEGRIDAD CRC: Archivo corrupto en ZIP: {bad_file}")
    print("[PASS] Verificacion CRC 100% integra.")

    os.replace(temp_zip, target_zip)
    final_size = os.path.getsize(target_zip)

    print(f"Calculando SHA-256 de {target_zip}...")
    zip_sha = calculate_sha256(target_zip)
    print(f"SHA-256: {zip_sha}")

    return {
        "zip_path": target_zip,
        "files_count": count,
        "uncompressed_bytes": total_uncompressed,
        "compressed_bytes": final_size,
        "compressed_gb": round(final_size / (1024**3), 3),
        "sha256": zip_sha,
        "integrity": "PASS"
    }

def run_rescue_pipeline():
    target_drive, status = get_verified_target_volume()
    if not target_drive:
        print(f"[BLOQUEO DE DESTINO] {status}")
        sys.exit(1)

    print("=" * 65)
    print("  TEITAN — RESCATE INTEGRAL VERIFICADO (TWO-STAGE ZIP64 V5)")
    print(f"  Destino Kingston Validado: {target_drive} ({status})")
    print("=" * 65)

    rescue_root = os.path.join(target_drive, "TEITAN_RESCUE_OUTPUT")
    stage_root = os.path.join(rescue_root, "STAGED_COPIES")
    os.makedirs(stage_root, exist_ok=True)

    # 1. Inicializar / Validar DB de Checkpoint SQLite
    checkpoint_db_path = os.path.join(rescue_root, "stage_checkpoint.sqlite")
    try:
        checkpoint_db = CheckpointDB(checkpoint_db_path)
    except Exception as e:
        print(f"\n[BLOQUEO DE CHECKPOINT] {e}")
        sys.exit(1)

    # 2. Compilar inventario maestro priorizado y registrar exclusiones
    print("\n[PASO 1] Validando raices y compilando Inventario Maestro Priorizado...")
    try:
        inv = build_prioritized_inventory()
    except Exception as e:
        print(f"\n[BLOQUEO DE INVENTARIO] {e}")
        sys.exit(1)

    # Persistir informe formal de exclusiones justificadas
    exclusions_path = os.path.join(rescue_root, "EXCLUSIONS_REPORT.json")
    with open(exclusions_path, "w", encoding="utf-8") as f:
        json.dump({
            "TIMESTAMP": time.strftime("%Y-%m-%d %H:%M:%S"),
            "TOTAL_EXCLUSIONS": len(inv["justified_exclusions"]),
            "EXCLUSIONS": inv["justified_exclusions"]
        }, f, indent=2)

    print(f"  Prioridad 1 (Digital Optimus): {inv['digital_optimus_count']} archivos")
    print(f"  Total Núcleo Crítico:          {len(inv['core'])} archivos")
    print(f"  Total Laboratorio AR88:         {len(inv['lab'])} archivos")
    print(f"  TOTAL MAESTRO A RESCATAR:       {inv['total_files']} archivos")
    print(f"  Exclusiones justificadas:       {len(inv['justified_exclusions'])} archivos (presence/*.lock)")

    # 3. Validación dinámica de espacio libre en Kingston
    free_bytes = shutil.disk_usage(target_drive).free
    # Calculamos bytes pendientes
    pending_bytes = 0
    for f in inv["master_all"]:
        norm = os.path.normcase(os.path.normpath(f))
        if not checkpoint_db.get_entry(norm):
            try:
                pending_bytes += os.path.getsize(f)
            except Exception:
                pass
    
    # Estimación conservadora de espacio requerido: pendientes + tamaño esperado de teitan.zip (~45-55 GB) + margen
    required_space = pending_bytes + (55 * 1024 * 1024 * 1024) + SAFETY_MARGIN_BYTES
    print(f"\n[EVALUACION DE ESPACIO DINAMICO]")
    print(f"  Espacio Libre en Kingston: {free_bytes / (1024**3):.2f} GiB")
    print(f"  Bytes pendientes de copia: {pending_bytes / (1024**3):.2f} GiB")
    print(f"  Espacio seguro requerido:  {required_space / (1024**3):.2f} GiB")

    if free_bytes < required_space:
        print(f"[BLOQUEO DE CAPACIDAD DINAMICA] Kingston tiene {free_bytes / (1024**3):.2f} GiB libres (requeridos {required_space / (1024**3):.2f} GiB).")
        sys.exit(1)

    # 4. Transferencia segura del núcleo en orden de prioridad
    core_manifest, core_errors = stage_copy_group(inv["core"], stage_root, checkpoint_db, "NUCLEO_CRITICO")

    if core_errors:
        print("\n[ABORTANDO POR SEGURIDAD] Se detectaron errores de lectura en el nucleo critico.")
        print(f"Errores: {len(core_errors)}. Se preserva lo recuperado y se detienen las lecturas en C:.")
        err_report = {"STATUS": "ABORTED_CORE_ERRORS", "ERRORS": core_errors}
        with open(os.path.join(rescue_root, "ERROR_REPORT.json"), "w", encoding="utf-8") as f:
            json.dump(err_report, f, indent=2)
        sys.exit(1)

    # 5. Generar teitan_core.zip leyendo exclusivamente de Kingston
    core_zip_path = os.path.join(rescue_root, "teitan_core.zip")
    core_res = preserve_and_create_zip_from_stage(core_manifest, core_zip_path, "teitan_core.zip (NUCLEO)")

    # 6. Transferencia de Laboratorio AR88
    lab_manifest, lab_errors = stage_copy_group(inv["lab"], stage_root, checkpoint_db, "LABORATORIO_AR88")

    all_manifest = core_manifest + lab_manifest
    all_errors = core_errors + lab_errors

    # 7. Reconciliación estricta de inventario
    expected_set = set(inv["master_all"])
    copied_set = set(item["source"] for item in all_manifest)
    missing_files = list(expected_set - copied_set)

    print(f"\n[RECONCILIACION]")
    print(f"  Esperados:  {len(expected_set)}")
    print(f"  Copiados:   {len(copied_set)}")
    print(f"  Faltantes:  {len(missing_files)}")

    if missing_files or all_errors:
        print("[ALERTA] Respaldo no completo al 100%. Preservando estado sin generar ZIP maestro incompleto.")
        err_report = {"STATUS": "INCOMPLETE", "MISSING": missing_files, "ERRORS": all_errors}
        with open(os.path.join(rescue_root, "ERROR_REPORT.json"), "w", encoding="utf-8") as f:
            json.dump(err_report, f, indent=2)
        sys.exit(1)

    # 8. Generar teitan.zip maestro leyendo exclusivamente de Kingston
    master_zip_path = os.path.join(rescue_root, "teitan.zip")
    master_res = preserve_and_create_zip_from_stage(all_manifest, master_zip_path, "teitan.zip (MAESTRO COMPLETO)")

    # 9. Resumen e informe consolidado
    summary = {
        "TIMESTAMP": time.strftime("%Y-%m-%d %H:%M:%S"),
        "TEITAN_CORE_ZIP": core_res,
        "TEITAN_MASTER_ZIP": master_res,
        "ARCHIVOS_ESPERADOS": len(expected_set),
        "ARCHIVOS_COPIADOS_Y_VERIFICADOS": len(copied_set),
        "EXCLUSIONES_JUSTIFICADAS": len(inv["justified_exclusions"]),
        "ARCHIVOS_FALTANTES": len(missing_files),
        "TOTAL_ERRORES": len(all_errors),
        "BACKUP_COMPLETED": (len(missing_files) == 0 and len(all_errors) == 0 and master_res["integrity"] == "PASS")
    }

    with open(os.path.join(rescue_root, "RESCUE_AUDIT_REPORT.json"), "w", encoding="utf-8") as f:
        json.dump(summary, f, indent=2)

    print("\n" + "=" * 65)
    print("  INFORME CONSOLIDADO FINAL")
    print("=" * 65)
    print(json.dumps(summary, indent=2))

    if summary["BACKUP_COMPLETED"]:
        sys.exit(0)
    else:
        sys.exit(1)

if __name__ == "__main__":
    if len(sys.argv) > 1 and sys.argv[1] == "run":
        run_rescue_pipeline()
    elif len(sys.argv) > 1 and sys.argv[1] == "inventory":
        inv = build_prioritized_inventory()
        print(f"DIGITAL_OPTIMUS_CORE:    {inv['digital_optimus_count']}")
        print(f"ARCHIVOS_CORE:           {len(inv['core'])}")
        print(f"ARCHIVOS_LAB:            {len(inv['lab'])}")
        print(f"ARCHIVOS_TOTAL:          {inv['total_files']}")
        print(f"EXCLUSIONES_PRESENCE:    {len(inv['justified_exclusions'])}")
    else:
        print("Motor preparado V5. Uso: python teitan_backup_engine.py [run | inventory]")
