# TAI-Storage Data Module

Módulo de transformación de archivos a pandas DataFrames con inferencia automática de tipos.

## 📦 Instalación

```bash
# Con pip
pip install tai-storage[data]

# Con poetry
poetry add tai-storage[data]

# Instalar todo
pip install tai-storage[all]
```

## 🎯 Características

- ✅ **Lectura de múltiples formatos**: CSV, Excel (.xlsx, .xls), Parquet, JSON
- ✅ **Inferencia automática de tipos**: Detecta enteros, flotantes, fechas, booleanos
- ✅ **Normalización de columnas**: Convierte nombres a snake_case automáticamente
- ✅ **Excel avanzado**: Soporte para entrypoints, rangos personalizados, múltiples hojas
- ✅ **Integración perfecta**: Extiende la clase `File` con capacidades de DataFrame
- ✅ **Filtrado de columnas**: Lee solo las columnas que necesitas

## 🚀 Uso Básico

### ✨ Opción Recomendada: Método `get_data()`

La forma más elegante de convertir archivos a DataFrames:

```python
from tai_storage import LocalOrigin

origin = LocalOrigin(root_path='/data')
file = origin.get_folder('reports').get_file('datos.csv')

# Auto-detecta el formato según la extensión
df = file.get_data().to_dataframe()

# CSV con opciones personalizadas
df = file.get_data().to_csv(sep=';', encoding='latin1', infer_types=True)

# Excel con hoja específica
df = file.get_data().to_excel(sheet_name='Ventas', infer_dimensions=True)

# Excel con punto de entrada
df = file.get_data().to_excel(
    entrypoint='Código',  # Busca celda con "Código"
    infer_dimensions=True,
    normalize_columns=True
)
```

**Ventajas:**
- 🎯 **API limpia y descubrible**: Autocomplete en el IDE
- 🔄 **Sin dependencia forzada**: Pandas solo se carga si usas `get_data()`
- 📦 **Caché automático**: Los bytes del archivo se leen una sola vez
- 🎨 **Separación de responsabilidades**: No modifica la jerarquía de clases

### Opción Alternativa: Usar Readers directamente

```python
from tai_storage import LocalOrigin
from tai_storage.data import CSVReader, ExcelReader

origin = LocalOrigin(root_path='/data')
file = origin.get_folder('reports').get_file('datos.csv')

# Leer CSV
reader = CSVReader()
df = reader.read(
    file.read(),
    infer_types=True,
    normalize_columns=True
)
```

## 📖 Ejemplos Detallados

### CSV con opciones avanzadas

```python
# Usando get_data() (recomendado)
file = origin.get_folder('data').get_file('ventas.csv')
df = file.get_data().to_csv(
    sep=';',                          # Separador personalizado
    encoding='latin1',                # Codificación
    normalize_columns=True,           # Normalizar nombres
    infer_types=True,                 # Inferir tipos
    with_fields=['id', 'nombre']      # Solo estas columnas
)

# O usando reader directamente
from tai_storage.data import CSVReader
reader = CSVReader()
df = reader.read(
    file.read(),
    sep=';',
    normalize_columns=True,
    infer_types=True
)
```

### Excel con punto de entrada

```python
# Usando get_data() (recomendado)
file = origin.get_folder('reportes').get_file('ventas.xlsx')
df = file.get_data().to_excel(
    sheet_name='Ventas',
    entrypoint='Código',      # Busca celda con "Código"
    infer_dimensions=True,    # Infiere tamaño automáticamente
    normalize_columns=True,
    infer_types=True
)

# O usando reader directamente
from tai_storage.data import ExcelReader
reader = ExcelReader()
df = reader.read(
    file.read(),
    sheet_name='Ventas',
    entrypoint='Código',
    infer_dimensions=True
)
```

### Excel con rango específico

```python
df = file.get_data().to_excel(
    sheet_name='Datos',
    entrypoint='A5',    # Datos empiezan en A5
    width=10,           # 10 columnas
    height=100          # 100 filas
)
```

### Todos los formatos soportados

```python
# Auto-detectar formato según extensión
df = file.get_data().to_dataframe()

# CSV con opciones
df = file.get_data().to_csv(sep=';', encoding='latin1')

# Excel con opciones
df = file.get_data().to_excel(
    sheet_name='Ventas',
    entrypoint='Código',
    infer_dimensions=True
)

# Parquet
df = file.get_data().to_parquet()

# JSON
df = file.get_data().to_json(orient='records')

# Solo ciertas columnas
df = file.get_data().to_dataframe(with_fields=['id', 'nombre', 'fecha'])
```

## 🔧 Inferencia de Tipos

El módulo detecta automáticamente:

```python
from tai_storage.data import TypeInferencer
import pandas as pd

# Enteros
series = pd.Series(['1', '2', '3'])
inferencer = TypeInferencer(series)
converted = inferencer.convert()  # → Int64

# Flotantes
series = pd.Series(['1.5', '2.3', '3.7'])
converted = TypeInferencer(series).convert()  # → Float64

# Fechas
series = pd.Series(['2024-01-31', '2024-02-01'])
converted = TypeInferencer(series).convert()  # → datetime64

# Con valores nulos
series = pd.Series(['1', '2', None, '4'])
converted = TypeInferencer(series).convert()  # → Int64 (nullable)
```

### Formatos de fecha soportados

- ISO: `2024-01-31`, `2024-01-31T15:30:45`
- Barras: `31/01/2024`, `2024/01/31`
- Guiones: `31-01-2024`, `2024-01-31`
- Puntos: `31.01.2024`
- Con hora: `2024-01-31 15:30:45`
- 12 horas: `2024-01-31 03:30:45 PM`
- Y más...

## 🎨 Normalización de Columnas

```python
from tai_storage.data import normalizar_texto

normalizar_texto('Nombre Completo')    # → 'nombre_completo'
normalizar_texto('FechaCreación')      # → 'fecha_creacion'
normalizar_texto('Código.Único')       # → 'codigo_unico'
normalizar_texto('ID Usuario')         # → 'id_usuario'
```

## 📊 Formatos Soportados

| Formato | Extensión | Reader | Características |
|---------|-----------|--------|-----------------|
| CSV | `.csv` | `CSVReader` | Separador personalizado, encoding |
| Excel | `.xlsx`, `.xls` | `ExcelReader` | Múltiples hojas, entrypoints, rangos |
| Parquet | `.parquet` | `ParquetReader` | Tipos nativos preservados |
| JSON | `.json` | `JSONReader` | Múltiples orientaciones |

## 🔍 API Completa

### CSVReader

```python
reader.read(
    file_bytes: bytes,
    column_names: list[str] = None,      # Nombres personalizados
    sep: str = ",",                      # Separador
    encoding: str = 'utf-8',             # Codificación
    normalize_columns: bool = False,     # Normalizar nombres
    infer_types: bool = True,            # Inferir tipos
    with_fields: list[str] = None        # Filtrar columnas
)
```

### ExcelReader

```python
reader.read(
    file_bytes: bytes,
    sheet_name: str = 'Hoja1',           # Hoja a leer
    column_names: list[str] = None,      # Nombres personalizados
    entrypoint: str = None,              # Celda inicio ('A5' o 'Código')
    width: int = None,                   # Ancho del rango
    height: int = None,                  # Alto del rango
    regex: bool = False,                 # Entrypoint como regex
    infer_dimensions: bool = False,      # Auto-detectar dimensiones
    normalize_columns: bool = False,     # Normalizar nombres
    infer_types: bool = True,            # Inferir tipos
    with_fields: list[str] = None        # Filtrar columnas
)
```

## 🧪 Testing

```bash
# Ejecutar tests del módulo data
pytest tests/test_data.py -v

# Ejecutar ejemplos
python examples/examples_data.py
```

## 💡 Casos de Uso

### Pipeline de ETL

```python
from tai_storage import StorageFactory

# Leer de Azure Blob
origin = StorageFactory.create_azure(...)
file = origin.get_folder('raw').get_file('data.csv')

# Transformar
df = file.get_data().to_dataframe(infer_types=True)
df_clean = df.dropna()
df_filtered = df_clean[df_clean['valor'] > 100]

# Guardar local
import pandas as pd
local_origin = StorageFactory.create_local('/processed')
output_file = local_origin.get_folder('').get_file('clean_data.parquet')
output_file.write(df_filtered.to_parquet())
```

### Análisis de múltiples archivos

```python
import pandas as pd

origin = LocalOrigin(root_path='/reports')
folder = origin.get_folder('2024/ventas')

# Leer todos los CSVs y combinar
dfs = []
for file in folder.list_files(pattern=r'.*\.csv$'):
    df = file.get_data().to_dataframe(normalize_columns=True, infer_types=True)
    dfs.append(df)

df_combined = pd.concat(dfs, ignore_index=True)
```

## 🐛 Troubleshooting

**Error: "Module 'data' requires pandas"**
```bash
pip install tai-storage[data]
```

**Excel: "Hoja no encontrada"**
```python
# Ver hojas disponibles primero
from openpyxl import load_workbook
wb = load_workbook(BytesIO(file.read()))
print(wb.sheetnames)
```

**Tipos no inferidos correctamente**
```python
# Desactivar inferencia y hacerlo manualmente
df = reader.read(file_bytes, infer_types=False)
df['fecha'] = pd.to_datetime(df['fecha'])
df['valor'] = pd.to_numeric(df['valor'])
```

## 📚 Más Información

- [Documentación principal](../README.md)
- [Ejemplos completos](../examples/examples_data.py)
- [Tests](../tests/test_data.py)
