tablas-python 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
helpers/__init__.py ADDED
@@ -0,0 +1,27 @@
1
+ """
2
+ Módulo de helpers para gestión, presentación, cálculo y exportación de tablas.
3
+ """
4
+
5
+ from .table_manager import TableManager, obtener_tabla, inspeccionar_archivo, exportar_archivo_a_csv
6
+ from .display_helper import DisplayHelper
7
+ from utils.batch_processor import unir_archivos_carpeta
8
+ from utils.excel_writer import escribir_en_excel
9
+ from utils.validator import validar_dataframe, reporte_calidad, detectar_duplicados
10
+ from utils.data_helpers import buscar_v, conciliar_tablas, obtener_celda, modificar_celda
11
+
12
+ __all__ = [
13
+ "TableManager",
14
+ "obtener_tabla",
15
+ "inspeccionar_archivo",
16
+ "exportar_archivo_a_csv",
17
+ "unir_archivos_carpeta",
18
+ "escribir_en_excel",
19
+ "validar_dataframe",
20
+ "reporte_calidad",
21
+ "detectar_duplicados",
22
+ "buscar_v",
23
+ "conciliar_tablas",
24
+ "obtener_celda",
25
+ "modificar_celda",
26
+ "DisplayHelper",
27
+ ]
@@ -0,0 +1,135 @@
1
+ """
2
+ Módulo helper para visualización limpia en consola de tablas detectadas y DataFrames.
3
+ """
4
+ from typing import List, Any
5
+ import sys
6
+ import pandas as pd
7
+
8
+ if sys.platform == "win32":
9
+ try:
10
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace")
11
+ except Exception:
12
+ pass
13
+
14
+ try:
15
+ from rich.console import Console
16
+ from rich.table import Table
17
+ from rich.panel import Panel
18
+ from rich.text import Text
19
+ HAS_RICH = True
20
+ console = Console(highlight=False)
21
+ except ImportError:
22
+ HAS_RICH = False
23
+ console = None
24
+
25
+
26
+ class DisplayHelper:
27
+ """Helper para presentar resúmenes visuales de tablas y datos crudos."""
28
+
29
+ @staticmethod
30
+ def print_header(title: str):
31
+ """Imprime un título estilizado."""
32
+ if HAS_RICH:
33
+ console.print(f"\n[bold cyan]=== {title} ===[/bold cyan]")
34
+ else:
35
+ print(f"\n=== {title} ===")
36
+
37
+ @staticmethod
38
+ def _get_location_str(t: Any) -> str:
39
+ """Obtiene la cadena descriptiva de ubicación (Página, Hoja, Celda o Tabla SQLite)."""
40
+ if hasattr(t, 'table_type'):
41
+ return f"SQLite: {t.table_name} ({t.table_type})"
42
+ elif hasattr(t, 'page_number'):
43
+ return f"PDF Pág. {t.page_number}"
44
+ elif hasattr(t, 'sheet_name'):
45
+ t_name = getattr(t, 'table_name', None)
46
+ range_addr = getattr(t, 'range_address', '') or getattr(t, 'start_cell', '')
47
+ if t_name and t_name != t.sheet_name:
48
+ return f"Excel Tabla '{t_name}' ({t.sheet_name} - {range_addr})"
49
+ elif range_addr and range_addr != "A1":
50
+ return f"Hoja: {t.sheet_name} ({range_addr})"
51
+ return f"Hoja: {t.sheet_name}"
52
+ return "N/A"
53
+
54
+ @staticmethod
55
+ def print_tables_summary(tables_info: List[Any], file_path: str):
56
+ """Muestra un resumen de todas las tablas encontradas en el archivo."""
57
+ if HAS_RICH:
58
+ table = Table(title=f"📋 Tablas detectadas en: {file_path}", show_lines=True)
59
+ table.add_column("ID Tabla", justify="center", style="bold green")
60
+ table.add_column("Ubicación / Nombre", justify="center", style="cyan")
61
+ table.add_column("Dimensiones (Filas x Cols)", justify="center")
62
+ table.add_column("Vista previa fila 1 y 2", style="dim")
63
+
64
+ for t in tables_info:
65
+ loc = DisplayHelper._get_location_str(t)
66
+ dims = f"{t.num_rows} x {t.num_cols}"
67
+ preview_rows = t.get_preview(2)
68
+ preview_str = " | ".join(str(r) for r in preview_rows)
69
+ if len(preview_str) > 60:
70
+ preview_str = preview_str[:57] + "..."
71
+ table.add_row(f"Tabla {t.table_id}", loc, dims, preview_str)
72
+
73
+ console.print(table)
74
+ else:
75
+ print(f"\n--- Resumen de tablas en {file_path} ---")
76
+ for t in tables_info:
77
+ loc = DisplayHelper._get_location_str(t)
78
+ print(f" [Tabla {t.table_id}] -> {loc} | Dimensiones: {t.num_rows} filas x {t.num_cols} columnas")
79
+
80
+ @staticmethod
81
+ def print_raw_preview(table_info: Any, max_rows: int = 8):
82
+ """
83
+ Muestra una vista previa de las primeras filas con sus números de fila (1-indexed).
84
+ Esto permite al usuario identificar fácilmente cuál fila contiene el encabezado.
85
+ """
86
+ raw_rows = table_info.get_preview(max_rows)
87
+ loc = DisplayHelper._get_location_str(table_info)
88
+
89
+ if HAS_RICH:
90
+ table = Table(
91
+ title=f"🔍 Vista Cruda: Tabla {table_info.table_id} ({loc}) - Primeras {len(raw_rows)} filas",
92
+ show_lines=True
93
+ )
94
+ table.add_column("Fila #", justify="center", style="bold yellow")
95
+
96
+ # Determinar máximo de columnas
97
+ max_cols = max(len(r) for r in raw_rows) if raw_rows else 0
98
+ for c in range(max_cols):
99
+ table.add_column(f"Col {c+1}", style="white")
100
+
101
+ for idx, row in enumerate(raw_rows, start=1):
102
+ cells = [str(val) if val is not None else "" for val in row]
103
+ # Rellenar columnas faltantes si la fila es corta
104
+ while len(cells) < max_cols:
105
+ cells.append("")
106
+ table.add_row(f"Fila {idx}", *cells)
107
+
108
+ console.print(table)
109
+ console.print("[italic dim]Consejo: Revisa los números de 'Fila #' para elegir tu 'fila_encabezado'.[/italic dim]\n")
110
+ else:
111
+ print(f"\n--- Vista Cruda: Tabla {table_info.table_id} ({loc}) ---")
112
+ for idx, row in enumerate(raw_rows, start=1):
113
+ row_str = " | ".join(str(val) if val is not None else "[vacío]" for val in row)
114
+ print(f" Fila {idx:2d}: {row_str}")
115
+ print("Consejo: Usa el número de 'Fila' para tu parámetro fila_encabezado.\n")
116
+
117
+ @staticmethod
118
+ def print_dataframe(df: pd.DataFrame, title: str = "DataFrame Limpio"):
119
+ """Imprime un DataFrame formateado."""
120
+ if HAS_RICH:
121
+ table = Table(title=f"✨ {title} (Total: {len(df)} filas x {len(df.columns)} columnas)", show_lines=True)
122
+ for col in df.columns:
123
+ table.add_column(str(col), style="cyan", justify="left")
124
+
125
+ # Mostrar hasta 10 filas
126
+ for _, row in df.head(10).iterrows():
127
+ table.add_row(*[str(val) if pd.notna(val) else "" for val in row.values])
128
+
129
+ console.print(table)
130
+ if len(df) > 10:
131
+ console.print(f"[dim]... y {len(df) - 10} filas más.[/dim]\n")
132
+ else:
133
+ print(f"\n=== {title} ===")
134
+ print(df.head(10))
135
+ print(f"Dimensiones: {df.shape[0]} filas x {df.shape[1]} columnas\n")
@@ -0,0 +1,418 @@
1
+ """
2
+ Módulo principal de gestión de tablas para interactuar fácilmente desde main.py.
3
+ Permite abrir archivos PDF (pdfplumber), Excel (xlwings - abierto o cerrado) y SQLite (.db/.sqlite),
4
+ inspeccionar qué tablas existen y extraer DataFrames limpios en pandas indicando
5
+ la tabla y la fila del encabezado o ejecutando consultas.
6
+ """
7
+
8
+ from typing import List, Optional, Union, Any, Dict
9
+ import os
10
+ import pandas as pd
11
+
12
+ from utils.pdf_extractor import PDFTableExtractor, RawTableInfo
13
+ from utils.excel_extractor import ExcelTableExtractor, RawExcelTableInfo
14
+ from utils.sqlite_extractor import SQLiteTableExtractor, RawSQLiteTableInfo
15
+ from utils.table_cleaner import TableCleaner
16
+ from utils.file_utils import resolve_file_path
17
+ from utils.exporter import TableExporter, guardar_csv, guardar_excel
18
+ from helpers.display_helper import DisplayHelper
19
+
20
+
21
+ class TableManager:
22
+ """
23
+ Gestor unificado de tablas para PDFs, Excel/CSV (xlwings) y bases de datos SQLite.
24
+
25
+ Ejemplos de uso en main.py:
26
+ --------------------------
27
+ # 1. PDF
28
+ doc = TableManager("samples/ejemplo_facturas.pdf")
29
+ df = doc.get_df(tabla=3, fila_encabezado=4)
30
+
31
+ # 2. Excel (detecta si está abierto o cerrado automáticamente)
32
+ doc_xl = TableManager("inventario.xlsx", archivo_abierto=None)
33
+ df_inv = doc_xl.get_df(tabla=1, fila_encabezado=4)
34
+
35
+ # 3. SQLite
36
+ db = TableManager("empresa.db")
37
+ df_ventas = db.get_df("ventas") # o db.get_df(tabla=1)
38
+ """
39
+
40
+ def __init__(
41
+ self,
42
+ file_path: str,
43
+ archivo_abierto: Optional[bool] = None,
44
+ preferir_xlwings: bool = True,
45
+ auto_load: bool = True
46
+ ):
47
+ """
48
+ Parámetros:
49
+ -----------
50
+ file_path : str
51
+ Ruta del archivo (relativa o absoluta).
52
+ archivo_abierto : bool o None (para Excel)
53
+ - True: se conecta al libro ya abierto en Excel.
54
+ - False: abre el libro cerrado en segundo plano.
55
+ - None: autodetecta si el archivo ya está abierto en Excel.
56
+ preferir_xlwings : bool (por defecto True)
57
+ Utiliza xlwings para Excel con fallback a openpyxl.
58
+ auto_load : bool (por defecto True)
59
+ Carga las tablas automáticamente al instanciar.
60
+ """
61
+ self.file_path = resolve_file_path(file_path)
62
+ if not os.path.exists(self.file_path):
63
+ raise FileNotFoundError(f"No se encontró el archivo: {self.file_path}")
64
+
65
+ self.ext = os.path.splitext(self.file_path)[1].lower()
66
+ self.archivo_abierto = archivo_abierto
67
+ self.preferir_xlwings = preferir_xlwings
68
+ self.tables: List[Union[RawTableInfo, RawExcelTableInfo, RawSQLiteTableInfo]] = []
69
+
70
+ valid_extensions = ['.pdf', '.xlsx', '.xls', '.xlsm', '.csv', '.db', '.sqlite', '.sqlite3', '.db3']
71
+ if self.ext not in valid_extensions:
72
+ raise ValueError(f"Extensión no soportada: {self.ext}. Formatos soportados: {valid_extensions}")
73
+
74
+ if auto_load:
75
+ self.cargar_tablas()
76
+
77
+ def cargar_tablas(
78
+ self,
79
+ paginas: Optional[List[int]] = None,
80
+ hojas: Optional[List[Union[str, int]]] = None,
81
+ pdf_settings: Optional[Dict[str, Any]] = None,
82
+ dividir_bloques_excel: bool = False
83
+ ) -> List[Any]:
84
+ """
85
+ Carga o recarga las tablas del archivo aplicando filtros de páginas u hojas.
86
+ """
87
+ if self.ext == '.pdf':
88
+ extractor = PDFTableExtractor(self.file_path)
89
+ self.tables = extractor.extract_all_tables(pages=paginas, table_settings=pdf_settings)
90
+ elif self.ext in ['.db', '.sqlite', '.sqlite3', '.db3']:
91
+ extractor = SQLiteTableExtractor(self.file_path)
92
+ self.tables = extractor.extract_all_tables()
93
+ else:
94
+ extractor = ExcelTableExtractor(
95
+ self.file_path,
96
+ archivo_abierto=self.archivo_abierto,
97
+ preferir_xlwings=self.preferir_xlwings
98
+ )
99
+ self.tables = extractor.extract_all_tables(sheets=hojas, split_blank_blocks=dividir_bloques_excel)
100
+
101
+ return self.tables
102
+
103
+ @property
104
+ def total_tablas(self) -> int:
105
+ """Cantidad total de tablas detectadas en el documento o base de datos."""
106
+ return len(self.tables)
107
+
108
+ def resumen(self):
109
+ """Muestra en consola un resumen claro de todas las tablas encontradas."""
110
+ if not self.tables:
111
+ print(f"⚠️ No se detectaron tablas en {self.file_path}.")
112
+ return
113
+ DisplayHelper.print_tables_summary(self.tables, self.file_path)
114
+
115
+ def ver_crudo(self, tabla: Union[int, str] = 1, max_filas: int = 8):
116
+ """
117
+ Muestra la vista previa cruda de una tabla con sus números de fila (1-indexed).
118
+ Muy útil para identificar exactamente en qué fila están los encabezados reales.
119
+ """
120
+ table_info = self._get_raw_table_info(tabla)
121
+ DisplayHelper.print_raw_preview(table_info, max_rows=max_filas)
122
+
123
+ def _get_raw_table_info(self, tabla: Union[int, str]) -> Any:
124
+ """Obtiene el objeto de información cruda de una tabla por su ID (1-indexed) o nombre."""
125
+ if isinstance(tabla, int):
126
+ if tabla < 1 or tabla > len(self.tables):
127
+ raise IndexError(
128
+ f"Tabla {tabla} no válida. El archivo tiene {len(self.tables)} tabla(s) detectada(s)."
129
+ )
130
+ return self.tables[tabla - 1]
131
+
132
+ # Buscar por nombre (para SQLite o Excel)
133
+ target_name = str(tabla).strip().lower()
134
+ for t in self.tables:
135
+ if hasattr(t, 'table_name') and t.table_name.lower() == target_name:
136
+ return t
137
+ if hasattr(t, 'sheet_name') and t.sheet_name.lower() == target_name:
138
+ return t
139
+
140
+ raise ValueError(f"No se encontró la tabla o pestaña '{tabla}' en {self.file_path}")
141
+
142
+ def get_df(
143
+ self,
144
+ tabla: Union[int, str] = 1,
145
+ fila_encabezado: Optional[Union[int, List[int], str]] = 1,
146
+ celda_inicio: Optional[str] = None,
147
+ celda: Optional[str] = None,
148
+ rango: Optional[str] = None,
149
+ hoja: Optional[Union[str, int]] = None,
150
+ skip_footer: int = 0,
151
+ eliminar_filas_vacias: bool = True,
152
+ eliminar_columnas_vacias: bool = True,
153
+ auto_inferir_tipos: bool = True,
154
+ ) -> pd.DataFrame:
155
+ """
156
+ Extrae y limpia la tabla deseada devolviendo un pandas DataFrame limpio.
157
+
158
+ Parámetros:
159
+ -----------
160
+ tabla : int o str (por defecto 1)
161
+ - int: Número de tabla (1-indexed). Ej: tabla=3.
162
+ - str: Nombre oficial de tabla en Excel (ej: 'TablaProductos'),
163
+ nombre de tabla en SQLite (ej: 'ventas'), o nombre de hoja.
164
+ fila_encabezado : int, list[int], 'auto' o None (por defecto 1)
165
+ Número de fila que contiene los nombres de columnas (1-indexed).
166
+ Ejemplo: fila_encabezado=4 descarta filas 1, 2, 3 como basura.
167
+ (En SQLite o cuando se usa celda_inicio/rango exacto, se suele usar 1).
168
+ celda_inicio / celda : str, opcional (ej: 'C4', 'B3')
169
+ En Excel, coordenada de la celda donde inicia la tabla.
170
+ rango : str, opcional (ej: 'C4:F20')
171
+ En Excel, rango exacto a extraer.
172
+ hoja : str o int, opcional
173
+ Nombre o índice de la hoja en Excel donde se ubica la celda o rango.
174
+ skip_footer : int (por defecto 0)
175
+ Cantidad de filas finales a descartar (por ejemplo notas al pie o totales).
176
+ eliminar_filas_vacias : bool (por defecto True)
177
+ Descarta filas completamente vacías.
178
+ eliminar_columnas_vacias : bool (por defecto True)
179
+ Descarta columnas completamente vacías.
180
+ auto_inferir_tipos : bool (por defecto True)
181
+ Limpia signos monetarios y convierte valores numéricos cuando sea posible.
182
+
183
+ Retorna:
184
+ --------
185
+ pd.DataFrame
186
+ DataFrame de pandas listo para ser usado.
187
+ """
188
+ # Caso especial para SQLite
189
+ if self.ext in ['.db', '.sqlite', '.sqlite3', '.db3']:
190
+ extractor = SQLiteTableExtractor(self.file_path)
191
+ return extractor.get_table_df(tabla)
192
+
193
+ # Caso extracción directa por celda_inicio o rango en Excel
194
+ target_cell = celda_inicio or celda
195
+ if target_cell or rango:
196
+ extractor = ExcelTableExtractor(
197
+ self.file_path,
198
+ archivo_abierto=self.archivo_abierto,
199
+ preferir_xlwings=self.preferir_xlwings
200
+ )
201
+ raw_matrix = extractor.extract_by_cell_or_range(
202
+ celda_inicio=target_cell,
203
+ rango=rango,
204
+ hoja=hoja
205
+ )
206
+ return TableCleaner.clean(
207
+ raw_data=raw_matrix,
208
+ header_row=fila_encabezado,
209
+ skip_footer=skip_footer,
210
+ drop_empty_rows=eliminar_filas_vacias,
211
+ drop_empty_cols=eliminar_columnas_vacias,
212
+ auto_clean_types=auto_inferir_tipos,
213
+ )
214
+
215
+ # Caso estándar por ID o por nombre de tabla/hoja
216
+ table_info = self._get_raw_table_info(tabla)
217
+
218
+ # Si la tabla es una Named Table de Excel que ya parte exactamente en su rango,
219
+ # los encabezados están en la fila 1 de dicha tabla
220
+ header_to_use = fila_encabezado
221
+ if hasattr(table_info, 'start_cell') and table_info.start_cell and getattr(table_info, 'table_name', None) != table_info.sheet_name:
222
+ # Es una tabla con nombre oficial que inicia en start_cell
223
+ if fila_encabezado == 1 or fila_encabezado == 4: # si el usuario no especificó otra cosa
224
+ header_to_use = 1
225
+
226
+ df = TableCleaner.clean(
227
+ raw_data=table_info.raw_data,
228
+ header_row=header_to_use,
229
+ skip_footer=skip_footer,
230
+ drop_empty_rows=eliminar_filas_vacias,
231
+ drop_empty_cols=eliminar_columnas_vacias,
232
+ auto_clean_types=auto_inferir_tipos,
233
+ )
234
+ return df
235
+
236
+ # Alias para máxima flexibilidad
237
+ get_tabla = get_df
238
+ get_table = get_df
239
+
240
+ def query(self, sql_query: str, params: Optional[Union[tuple, dict]] = None) -> pd.DataFrame:
241
+ """
242
+ Ejecuta una consulta SQL si el archivo cargado es una base de datos SQLite.
243
+ """
244
+ if self.ext not in ['.db', '.sqlite', '.sqlite3', '.db3']:
245
+ raise ValueError("El método query() solo está disponible para bases de datos SQLite.")
246
+ extractor = SQLiteTableExtractor(self.file_path)
247
+ return extractor.query_df(sql_query, params=params)
248
+
249
+ def exportar(
250
+ self,
251
+ df: pd.DataFrame,
252
+ ruta_salida: str,
253
+ formato: Optional[str] = None,
254
+ sep: str = ";",
255
+ encoding: str = "utf-8-sig",
256
+ index: bool = False
257
+ ) -> str:
258
+ """
259
+ Exporta un DataFrame a Excel o CSV.
260
+ """
261
+ if formato is None:
262
+ _, ext = os.path.splitext(ruta_salida)
263
+ formato = ext.replace(".", "").lower()
264
+ if not formato:
265
+ formato = "csv" if ruta_salida.lower().endswith(".csv") else "xlsx"
266
+
267
+ if formato in ['csv']:
268
+ return TableExporter.to_csv(df, output_path=ruta_salida, sep=sep, encoding=encoding, index=index)
269
+ elif formato in ['xlsx', 'excel']:
270
+ return TableExporter.to_excel(df, output_path=ruta_salida, index=index)
271
+ else:
272
+ raise ValueError(f"Formato no soportado: {formato}. Use 'csv' o 'xlsx'.")
273
+
274
+ def exportar_csv(
275
+ self,
276
+ df: pd.DataFrame,
277
+ ruta_salida: str,
278
+ sep: str = ";",
279
+ encoding: str = "utf-8-sig",
280
+ index: bool = False
281
+ ) -> str:
282
+ """Exporta un DataFrame a CSV con separador y codificación optimizados para Excel."""
283
+ return TableExporter.to_csv(df, output_path=ruta_salida, sep=sep, encoding=encoding, index=index)
284
+
285
+ def exportar_todas_a_csv(
286
+ self,
287
+ directorio_salida: str,
288
+ fila_encabezado: Optional[Union[int, List[int], str]] = 1,
289
+ sep: str = ";",
290
+ encoding: str = "utf-8-sig",
291
+ index: bool = False,
292
+ **clean_kwargs
293
+ ) -> List[str]:
294
+ """
295
+ Extrae y exporta TODAS las tablas encontradas en el archivo a archivos CSV individuales.
296
+
297
+ Parámetros:
298
+ -----------
299
+ directorio_salida : str
300
+ Directorio donde se guardarán los archivos CSV.
301
+ fila_encabezado : int, 'auto', etc.
302
+ Fila de encabezado a aplicar para tablas de PDF/Excel.
303
+
304
+ Retorna:
305
+ --------
306
+ List[str]
307
+ Lista con las rutas de todos los archivos CSV generados.
308
+ """
309
+ tables_dict: Dict[str, pd.DataFrame] = {}
310
+ base_name = os.path.splitext(os.path.basename(self.file_path))[0]
311
+
312
+ for idx, t_info in enumerate(self.tables, start=1):
313
+ if self.ext in ['.db', '.sqlite', '.sqlite3', '.db3']:
314
+ t_name = getattr(t_info, 'table_name', f"tabla_{idx}")
315
+ df = self.get_df(tabla=t_name)
316
+ key = f"{base_name}_{t_name}"
317
+ else:
318
+ t_name = getattr(t_info, 'table_name', None) or getattr(t_info, 'sheet_name', None)
319
+ if t_name and t_name != "CSV":
320
+ key = f"{base_name}_{t_name}"
321
+ elif hasattr(t_info, 'page_number'):
322
+ key = f"{base_name}_pag_{t_info.page_number}_tab_{t_info.table_in_page}"
323
+ else:
324
+ key = f"{base_name}_tabla_{idx}"
325
+
326
+ df = self.get_df(tabla=idx, fila_encabezado=fila_encabezado, **clean_kwargs)
327
+
328
+ tables_dict[key] = df
329
+
330
+ saved_files = TableExporter.export_batch_to_csv(
331
+ tables_dict=tables_dict,
332
+ output_dir=directorio_salida,
333
+ sep=sep,
334
+ encoding=encoding,
335
+ index=index
336
+ )
337
+ return saved_files
338
+
339
+
340
+ def obtener_tabla(
341
+ archivo: str,
342
+ tabla: Union[int, str] = 1,
343
+ fila_encabezado: Optional[Union[int, List[int], str]] = 1,
344
+ celda_inicio: Optional[str] = None,
345
+ celda: Optional[str] = None,
346
+ rango: Optional[str] = None,
347
+ hoja: Optional[Union[str, int]] = None,
348
+ skip_footer: int = 0,
349
+ archivo_abierto: Optional[bool] = None,
350
+ **kwargs
351
+ ) -> pd.DataFrame:
352
+ """
353
+ Función de una sola línea para extraer directamente un DataFrame desde cualquier PDF, Excel o SQLite.
354
+
355
+ Ejemplos:
356
+ ---------
357
+ # PDF (Tabla 3, encabezado en fila 4):
358
+ df = obtener_tabla("facturas.pdf", tabla=3, fila_encabezado=4)
359
+
360
+ # Excel por Celda de Inicio (ej: tabla parte en la celda C4):
361
+ df = obtener_tabla("inventario.xlsx", celda_inicio="C4", hoja="Inventario")
362
+
363
+ # Excel por Nombre Oficial de Tabla:
364
+ df = obtener_tabla("inventario.xlsx", tabla="TablaProductos")
365
+
366
+ # Excel por Rango exacto:
367
+ df = obtener_tabla("inventario.xlsx", rango="B3:F15")
368
+
369
+ # SQLite por Nombre de Tabla:
370
+ df = obtener_tabla("empresa.db", tabla="ventas")
371
+ """
372
+ manager = TableManager(archivo, archivo_abierto=archivo_abierto)
373
+ return manager.get_df(
374
+ tabla=tabla,
375
+ fila_encabezado=fila_encabezado,
376
+ celda_inicio=celda_inicio,
377
+ celda=celda,
378
+ rango=rango,
379
+ hoja=hoja,
380
+ skip_footer=skip_footer,
381
+ **kwargs
382
+ )
383
+
384
+
385
+ def inspeccionar_archivo(archivo: str, max_filas_preview: int = 8, archivo_abierto: Optional[bool] = None):
386
+ """
387
+ Función de ayuda rápida para ver todas las tablas y sus primeras filas con números de fila.
388
+ """
389
+ manager = TableManager(archivo, archivo_abierto=archivo_abierto)
390
+ manager.resumen()
391
+ for i in range(1, manager.total_tablas + 1):
392
+ manager.ver_crudo(tabla=i, max_filas=max_filas_preview)
393
+
394
+
395
+ def exportar_archivo_a_csv(
396
+ archivo: str,
397
+ carpeta_salida: str,
398
+ fila_encabezado: Optional[Union[int, List[int], str]] = 1,
399
+ sep: str = ";",
400
+ encoding: str = "utf-8-sig",
401
+ archivo_abierto: Optional[bool] = None,
402
+ **kwargs
403
+ ) -> List[str]:
404
+ """
405
+ Función de una sola línea para extraer y guardar TODAS las tablas de un archivo en archivos CSV individuales.
406
+
407
+ Ejemplo:
408
+ --------
409
+ archivos_guardados = exportar_archivo_a_csv("facturas.pdf", carpeta_salida="exports/facturas", fila_encabezado=4)
410
+ """
411
+ manager = TableManager(archivo, archivo_abierto=archivo_abierto)
412
+ return manager.exportar_todas_a_csv(
413
+ directorio_salida=carpeta_salida,
414
+ fila_encabezado=fila_encabezado,
415
+ sep=sep,
416
+ encoding=encoding,
417
+ **kwargs
418
+ )
@@ -0,0 +1,68 @@
1
+ """
2
+ tablas-python: Suite integral para extracción, transformación, conciliación y exportación de tablas en pandas.
3
+ """
4
+
5
+ from helpers.table_manager import (
6
+ TableManager,
7
+ obtener_tabla,
8
+ inspeccionar_archivo,
9
+ exportar_archivo_a_csv,
10
+ )
11
+ from helpers.display_helper import DisplayHelper
12
+ from utils.exporter import TableExporter, guardar_csv, guardar_excel
13
+ from utils.excel_writer import escribir_en_excel
14
+ from utils.batch_processor import unir_archivos_carpeta
15
+ from utils.validator import DataValidator, validar_dataframe, reporte_calidad, detectar_duplicados
16
+ from utils.data_helpers import (
17
+ limpiar_numero,
18
+ limpiar_columnas_numericas,
19
+ normalizar_fechas,
20
+ formato_moneda,
21
+ formato_porcentaje,
22
+ formato_miles,
23
+ formatear_dataframe,
24
+ agregar_fila_totales,
25
+ calcular_participacion,
26
+ calcular_variacion,
27
+ aplicar_impuesto,
28
+ agrupar_y_resumir,
29
+ obtener_celda,
30
+ modificar_celda,
31
+ buscar_v,
32
+ conciliar_tablas,
33
+ )
34
+
35
+ __version__ = "0.1.0"
36
+
37
+ __all__ = [
38
+ "TableManager",
39
+ "obtener_tabla",
40
+ "inspeccionar_archivo",
41
+ "exportar_archivo_a_csv",
42
+ "DisplayHelper",
43
+ "TableExporter",
44
+ "guardar_csv",
45
+ "guardar_excel",
46
+ "escribir_en_excel",
47
+ "unir_archivos_carpeta",
48
+ "DataValidator",
49
+ "validar_dataframe",
50
+ "reporte_calidad",
51
+ "detectar_duplicados",
52
+ "limpiar_numero",
53
+ "limpiar_columnas_numericas",
54
+ "normalizar_fechas",
55
+ "formato_moneda",
56
+ "formato_porcentaje",
57
+ "formato_miles",
58
+ "formatear_dataframe",
59
+ "agregar_fila_totales",
60
+ "calcular_participacion",
61
+ "calcular_variacion",
62
+ "aplicar_impuesto",
63
+ "agrupar_y_resumir",
64
+ "obtener_celda",
65
+ "modificar_celda",
66
+ "buscar_v",
67
+ "conciliar_tablas",
68
+ ]