# 🛸 Antigravity PySpark Framework

Enterprise framework para resolver problemas complejos de Big Data en AWS Glue y entornos PySpark puros sin esfuerzo. Este proyecto estandariza las mejores prácticas de la industria con un solo `import`.

## 📦 Empaquetado y Licenciamiento Comercial (SaaS B2B)

Si deseas vender y distribuir este framework a una empresa externa, es imperativo **proteger tu Propiedad Intelectual** (ofuscar el código fuente) y **venderlo con una licencia temporal** (para que te paguen la renovación).

### 1. Ofuscar el código y armar la "Bomba de Tiempo" (Protección B2B)
**IMPORTANTE:** Para garantizar la compatibilidad con los servidores de AWS Glue (que usan Python 3.11), es obligatorio realizar la compilación dentro de un contenedor Docker. Si compilas directamente en tu Mac, el archivo resultante fallará con un error de `Bad Magic Number`.

```bash
# Navega a la carpeta del framework
cd antigravity

# Detona la compilación segura (Docker inyectará la fecha de vencimiento y ofuscará)
chmod +x build_commercial_docker.sh
./build_commercial_docker.sh
```
Este proceso genera el archivo compilado en `dist/ag_framework_commercial.zip`. Cualquier ingeniero externo que intente abrir tus archivos allí dentro verá puro Bytecode binario indescifrable.

### 2. Construcción de uso interno / Open-Source (`.whl`)
Para el empaquetado inicial (sin ofuscación) para probar el framework dentro de tu propio AWS Glue, ejecuta el empaquetado estándar de Python:
```bash
# Construye el antigravity-0.1.0-py3-none-any.whl en la carpeta dist/
pip install build
python -m build
```

---

## 🚀 Guía de Uso Rápido para Ingenieros

### Motor A: Optimizador Core (`DataFrameChunker`)
**¿Cuándo usarlo?** Cuando lidias con DataFrames anchos (Wide DataFrames) o realizas analítica que provoca errores de `OutOfMemory` (OOM), demoras extremas, o fallos de `StackOverflowError`. Funciona fluidamente con cualquier volumen de datos y columnas, auto-regulando la memoria del clúster para acelerar los cruces matemáticos pesados.

**¿Cómo funciona?**
Particiona el DataFrame verticalmente e inyecta `localCheckpoint()` para aislar la memoria del Catalyst Optimizer.

```python
from antigravity.core import DataFrameChunker

# 1. Instancias el chunker indicando la llave primaria y cuántas columnas procesar a la vez.
chunker = DataFrameChunker(df_masivo, id_cols=["id_cliente"], chunk_size=50)

# 2. Defines tú lógica matemática. Spark solo le pasará 50 variables por iteración.
def mi_logica(chunk_df, indice):
    # Calcula algo y retorna un DF más pequeño
    return chunk_df.select("id_cliente", ...)

# 3. El framework ejecuta el bucle de forma segura en el clúster
resultados_parciales = chunker.process_chunks(mi_logica)
```

### Motor B: Generador Legacy (`LegacyTextTransformer`)
**¿Cuándo usarlo?** Cuando el negocio, banco, o gobierno te exige generar archivos `.TXT` planos, asimétricos o posicionales a partir de datos tabulares modernos.

**¿Cómo funciona?**
Recibe el DataFrame y usa `RDD.flatMap` para aplicar lógica de python puro distribuidamente.

```python
from antigravity.legacy import LegacyTextTransformer

# 1. Escribes las reglas del negocio (1 Fila de Spark = N líneas de texto)
def reglas_bancarias(fila):
    yield f"HEADER_{fila.id}"
    yield f"BODY_{fila.saldo}"

# 2. Invocas el framework
transformer = LegacyTextTransformer(df)

# 3. Generas la salida
transformer.save_as_text(reglas_bancarias, "s3://mi-bucket/banco_txt/")
```

### Motor IO: Smart Exporter (`S3Finalizer`)
**¿Cuándo usarlo?** Cuando detestas que PySpark guarde tus archivos como `part-0000X.csv` y deje basura como los archivos `_SUCCESS` en tu S3.

**¿Cómo funciona?**
Se conecta mediante `boto3` para listar, ordenar, secuenciar y renombrar la salida de Spark.

```python
from antigravity.io import S3Finalizer

# 1. Escribes tu DataFrame normal en Spark
df.write.parquet("s3://mi-bucket/salida_sucia/")

# 2. Invocas el framework para secuenciarlos inteligentemente e inyectar el formato
finalizador = S3Finalizer(bucket_name="mi-bucket")
finalizador.sequence_spark_outputs(
    s3_prefix="salida_sucia/",
    pattern="MI_EMPRESA_DATA_{seq:04d}.parquet" # Resultado: MI_EMPRESA_DATA_0001.parquet
)
```
