tablas-python 0.1.1__tar.gz → 0.1.2__tar.gz

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.
Files changed (27) hide show
  1. {tablas_python-0.1.1 → tablas_python-0.1.2}/PKG-INFO +1 -1
  2. {tablas_python-0.1.1 → tablas_python-0.1.2}/helpers/display_helper.py +66 -19
  3. {tablas_python-0.1.1 → tablas_python-0.1.2}/pyproject.toml +1 -1
  4. {tablas_python-0.1.1 → tablas_python-0.1.2}/tablas_python/__init__.py +1 -1
  5. {tablas_python-0.1.1 → tablas_python-0.1.2}/tablas_python.egg-info/PKG-INFO +1 -1
  6. {tablas_python-0.1.1 → tablas_python-0.1.2}/utils/data_helpers.py +141 -51
  7. {tablas_python-0.1.1 → tablas_python-0.1.2}/LICENSE +0 -0
  8. {tablas_python-0.1.1 → tablas_python-0.1.2}/README.md +0 -0
  9. {tablas_python-0.1.1 → tablas_python-0.1.2}/helpers/__init__.py +0 -0
  10. {tablas_python-0.1.1 → tablas_python-0.1.2}/helpers/table_manager.py +0 -0
  11. {tablas_python-0.1.1 → tablas_python-0.1.2}/setup.cfg +0 -0
  12. {tablas_python-0.1.1 → tablas_python-0.1.2}/tablas_python/cli.py +0 -0
  13. {tablas_python-0.1.1 → tablas_python-0.1.2}/tablas_python.egg-info/SOURCES.txt +0 -0
  14. {tablas_python-0.1.1 → tablas_python-0.1.2}/tablas_python.egg-info/dependency_links.txt +0 -0
  15. {tablas_python-0.1.1 → tablas_python-0.1.2}/tablas_python.egg-info/entry_points.txt +0 -0
  16. {tablas_python-0.1.1 → tablas_python-0.1.2}/tablas_python.egg-info/requires.txt +0 -0
  17. {tablas_python-0.1.1 → tablas_python-0.1.2}/tablas_python.egg-info/top_level.txt +0 -0
  18. {tablas_python-0.1.1 → tablas_python-0.1.2}/utils/__init__.py +0 -0
  19. {tablas_python-0.1.1 → tablas_python-0.1.2}/utils/batch_processor.py +0 -0
  20. {tablas_python-0.1.1 → tablas_python-0.1.2}/utils/excel_extractor.py +0 -0
  21. {tablas_python-0.1.1 → tablas_python-0.1.2}/utils/excel_writer.py +0 -0
  22. {tablas_python-0.1.1 → tablas_python-0.1.2}/utils/exporter.py +0 -0
  23. {tablas_python-0.1.1 → tablas_python-0.1.2}/utils/file_utils.py +0 -0
  24. {tablas_python-0.1.1 → tablas_python-0.1.2}/utils/pdf_extractor.py +0 -0
  25. {tablas_python-0.1.1 → tablas_python-0.1.2}/utils/sqlite_extractor.py +0 -0
  26. {tablas_python-0.1.1 → tablas_python-0.1.2}/utils/table_cleaner.py +0 -0
  27. {tablas_python-0.1.1 → tablas_python-0.1.2}/utils/validator.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tablas-python
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: Suite modular para extraer, transformar, conciliar y exportar tablas desde PDF, Excel y SQLite a pandas.
5
5
  Author: Reciba
6
6
  License: MIT
@@ -1,7 +1,7 @@
1
1
  """
2
2
  Módulo helper para visualización limpia en consola de tablas detectadas y DataFrames.
3
3
  """
4
- from typing import List, Any
4
+ from typing import List, Any, Optional, Union
5
5
  import sys
6
6
  import pandas as pd
7
7
 
@@ -115,34 +115,81 @@ class DisplayHelper:
115
115
  print("Consejo: Usa el número de 'Fila' para tu parámetro fila_encabezado.\n")
116
116
 
117
117
  @staticmethod
118
- def print_dataframe(df: pd.DataFrame, title: str = "DataFrame Limpio"):
119
- """Imprime un DataFrame formateado."""
118
+ def print_dataframe(
119
+ df: pd.DataFrame,
120
+ title: str = "DataFrame Limpio",
121
+ max_rows: Optional[Union[int, str]] = 15
122
+ ):
123
+ """
124
+ Imprime un DataFrame formateado con estilo Rich.
125
+
126
+ Parámetros:
127
+ -----------
128
+ df : pd.DataFrame
129
+ El DataFrame a mostrar.
130
+ title : str
131
+ Título de la tabla.
132
+ max_rows : int, None o 'all' (por defecto 15)
133
+ Cantidad máxima de filas a renderizar. Usa None o 'all' para mostrar todas las filas.
134
+ """
135
+ if df is None or df.empty:
136
+ if HAS_RICH:
137
+ console.print(f"[yellow]⚠️ {title}: El DataFrame está vacío o es None.[/yellow]")
138
+ else:
139
+ print(f"⚠️ {title}: El DataFrame está vacío o es None.")
140
+ return
141
+
142
+ total_filas = len(df)
143
+ if max_rows is None or max_rows == "all" or (isinstance(max_rows, int) and max_rows <= 0):
144
+ df_mostrar = df
145
+ limite = total_filas
146
+ else:
147
+ limite = int(max_rows)
148
+ df_mostrar = df.head(limite)
149
+
120
150
  if HAS_RICH:
121
- table = Table(title=f"✨ {title} (Total: {len(df)} filas x {len(df.columns)} columnas)", show_lines=True)
151
+ table = Table(
152
+ title=f"✨ {title} (Mostrando {len(df_mostrar)} de {total_filas} filas x {len(df.columns)} columnas)",
153
+ show_lines=True
154
+ )
122
155
  for col in df.columns:
123
156
  table.add_column(str(col), style="cyan", justify="left")
124
-
125
- # Mostrar hasta 10 filas
126
- for _, row in df.head(10).iterrows():
157
+
158
+ for _, row in df_mostrar.iterrows():
127
159
  table.add_row(*[str(val) if pd.notna(val) else "" for val in row.values])
128
-
160
+
129
161
  console.print(table)
130
- if len(df) > 10:
131
- console.print(f"[dim]... y {len(df) - 10} filas más.[/dim]\n")
162
+ if total_filas > limite:
163
+ console.print(f"[dim]... y {total_filas - limite} filas más. (Pasa max_rows=None para ver todas)[/dim]\n")
132
164
  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")
165
+ print(f"\n=== {title} (Mostrando {len(df_mostrar)} de {total_filas} filas) ===")
166
+ print(df_mostrar)
167
+ if total_filas > limite:
168
+ print(f"... y {total_filas - limite} filas más.\n")
136
169
 
137
170
 
138
- def mostrar_tabla(df: pd.DataFrame, title: str = "DataFrame", max_rows: int = 15):
171
+ def mostrar_tabla(
172
+ df: pd.DataFrame,
173
+ title: str = "DataFrame",
174
+ max_rows: Optional[Union[int, str]] = 15
175
+ ):
139
176
  """
140
177
  Imprime un DataFrame formateado con bordes y colores en consola usando Rich.
141
178
 
142
- Ejemplo:
143
- --------
144
- df = obtener_tabla("facturas.pdf", tabla=1)
145
- mostrar_tabla(df, "Mis Facturas")
179
+ Parámetros:
180
+ -----------
181
+ df : pd.DataFrame
182
+ El DataFrame a visualizar.
183
+ title : str
184
+ Título de la tabla.
185
+ max_rows : int, None o 'all' (por defecto 15)
186
+ Número de filas a mostrar. Usa max_rows=50, max_rows=None o max_rows='all' para mostrar todas.
187
+
188
+ Ejemplos:
189
+ ---------
190
+ mostrar_tabla(df, "Mis Facturas", max_rows=30) # Muestra hasta 30 filas
191
+ mostrar_tabla(df, "Todas las Facturas", max_rows=None) # Muestra todas las filas sin límite
146
192
  """
147
- DisplayHelper.print_dataframe(df, title=title)
193
+ DisplayHelper.print_dataframe(df, title=title, max_rows=max_rows)
194
+
148
195
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tablas-python"
7
- version = "0.1.1"
7
+ version = "0.1.2"
8
8
  description = "Suite modular para extraer, transformar, conciliar y exportar tablas desde PDF, Excel y SQLite a pandas."
9
9
  readme = "README.md"
10
10
  authors = [
@@ -32,7 +32,7 @@ from utils.data_helpers import (
32
32
  conciliar_tablas,
33
33
  )
34
34
 
35
- __version__ = "0.1.1"
35
+ __version__ = "0.1.2"
36
36
 
37
37
  __all__ = [
38
38
  "TableManager",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tablas-python
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: Suite modular para extraer, transformar, conciliar y exportar tablas desde PDF, Excel y SQLite a pandas.
5
5
  Author: Reciba
6
6
  License: MIT
@@ -240,12 +240,19 @@ def calcular_participacion(
240
240
  df: pd.DataFrame,
241
241
  columna_valor: str,
242
242
  nombre_col: str = "% Participación",
243
- decimales: int = 1
243
+ decimales: int = 1,
244
+ mostrar_feedback: bool = True
244
245
  ) -> pd.DataFrame:
245
246
  """
246
247
  Calcula el porcentaje de participación de cada fila sobre el total de la columna.
247
248
  """
248
249
  df_out = df.copy()
250
+ if df_out.empty:
251
+ return df_out
252
+ if columna_valor not in df_out.columns:
253
+ if mostrar_feedback:
254
+ print(f"⚠️ [calcular_participacion] La columna '{columna_valor}' no existe. Columnas disponibles: {list(df_out.columns)}")
255
+ return df_out
249
256
  total = df_out[columna_valor].sum()
250
257
  if total != 0 and pd.notna(total):
251
258
  df_out[nombre_col] = ((df_out[columna_valor] / total) * 100).round(decimales)
@@ -259,12 +266,20 @@ def calcular_variacion(
259
266
  col_actual: str,
260
267
  col_anterior: str,
261
268
  nombre_col: str = "% Variación",
262
- decimales: int = 1
269
+ decimales: int = 1,
270
+ mostrar_feedback: bool = True
263
271
  ) -> pd.DataFrame:
264
272
  """
265
273
  Calcula la variación porcentual entre dos columnas: ((actual - anterior) / anterior) * 100.
266
274
  """
267
275
  df_out = df.copy()
276
+ if df_out.empty:
277
+ return df_out
278
+ faltantes = [c for c in [col_actual, col_anterior] if c not in df_out.columns]
279
+ if faltantes:
280
+ if mostrar_feedback:
281
+ print(f"⚠️ [calcular_variacion] Columnas no encontradas: {faltantes}. Columnas disponibles: {list(df_out.columns)}")
282
+ return df_out
268
283
  ant = df_out[col_anterior]
269
284
  act = df_out[col_actual]
270
285
  var = np.where(ant != 0, ((act - ant) / ant.abs()) * 100, 0.0)
@@ -277,12 +292,19 @@ def aplicar_impuesto(
277
292
  col_neto: str,
278
293
  tasa: float = 0.19,
279
294
  col_iva: str = "IVA (19%)",
280
- col_total: str = "Total Bruto"
295
+ col_total: str = "Total Bruto",
296
+ mostrar_feedback: bool = True
281
297
  ) -> pd.DataFrame:
282
298
  """
283
299
  Calcula el IVA (o impuesto) y el valor bruto a partir de una columna neta.
284
300
  """
285
301
  df_out = df.copy()
302
+ if df_out.empty:
303
+ return df_out
304
+ if col_neto not in df_out.columns:
305
+ if mostrar_feedback:
306
+ print(f"⚠️ [aplicar_impuesto] La columna '{col_neto}' no existe en el DataFrame. Columnas disponibles: {list(df_out.columns)}")
307
+ return df_out
286
308
  df_out[col_iva] = (df_out[col_neto] * tasa).round(2)
287
309
  df_out[col_total] = (df_out[col_neto] + df_out[col_iva]).round(2)
288
310
  return df_out
@@ -291,7 +313,8 @@ def aplicar_impuesto(
291
313
  def agrupar_y_resumir(
292
314
  df: pd.DataFrame,
293
315
  por: Union[str, List[str]],
294
- metricas: Dict[str, Union[str, List[str]]]
316
+ metricas: Dict[str, Union[str, List[str]]],
317
+ mostrar_feedback: bool = True
295
318
  ) -> pd.DataFrame:
296
319
  """
297
320
  Agrupa un DataFrame y calcula sumas, promedios o conteos en una sola línea.
@@ -299,7 +322,20 @@ def agrupar_y_resumir(
299
322
  Ejemplo:
300
323
  resumen = agrupar_y_resumir(df, por='Categoria', metricas={'Subtotal': 'sum', 'Cantidad': 'sum'})
301
324
  """
302
- agrupado = df.groupby(por).agg(metricas).reset_index()
325
+ if df.empty:
326
+ return pd.DataFrame()
327
+ cols_por = [por] if isinstance(por, str) else list(por)
328
+ cols_faltantes = [c for c in cols_por if c not in df.columns]
329
+ if cols_faltantes:
330
+ if mostrar_feedback:
331
+ print(f"⚠️ [agrupar_y_resumir] Columnas de agrupación no encontradas: {cols_faltantes}. Columnas disponibles: {list(df.columns)}")
332
+ return df
333
+ metricas_validas = {k: v for k, v in metricas.items() if k in df.columns}
334
+ if not metricas_validas:
335
+ if mostrar_feedback:
336
+ print(f"⚠️ [agrupar_y_resumir] Ninguna de las columnas de métricas {list(metricas.keys())} existe en el DataFrame.")
337
+ return df
338
+ agrupado = df.groupby(por).agg(metricas_validas).reset_index()
303
339
  return agrupado
304
340
 
305
341
 
@@ -307,10 +343,15 @@ def obtener_celda(
307
343
  df: pd.DataFrame,
308
344
  fila: Any,
309
345
  columna: str,
310
- columna_identificador: Optional[str] = None
346
+ columna_identificador: Optional[str] = None,
347
+ default: Any = None,
348
+ lanzar_error: bool = False,
349
+ mostrar_feedback: bool = True
311
350
  ) -> Any:
312
351
  """
313
352
  Obtiene el valor de una celda puntual indicando el nombre/etiqueta de la fila y el nombre de la columna.
353
+ Si no se encuentra, muestra un mensaje de feedback amigable y devuelve un valor por defecto (None)
354
+ en lugar de interrumpir el programa con un error.
314
355
 
315
356
  Parámetros:
316
357
  -----------
@@ -321,19 +362,37 @@ def obtener_celda(
321
362
  columna : str
322
363
  El nombre de la columna deseada (ej: 'Precio Unitario', 'monto_neto').
323
364
  columna_identificador : str, opcional
324
- Si el DataFrame no tiene como índice los nombres de filas, especifica qué columna
325
- contiene el nombre buscado (ej: 'Codigo' o 'Producto'). Si es None, busca automáticamente.
365
+ Columna donde buscar el identificador. Si es None, busca automáticamente.
366
+ default : Any (por defecto None)
367
+ Valor a retornar si no se encuentra la fila o columna.
368
+ lanzar_error : bool (por defecto False)
369
+ Si es True, lanza KeyError en lugar de retornar el valor default.
370
+ mostrar_feedback : bool (por defecto True)
371
+ Si es True, imprime una advertencia informativa con sugerencias cuando no encuentra el dato.
326
372
 
327
373
  Ejemplos:
328
374
  ---------
329
- # Caso 1: Con índice asignado
330
- precio = obtener_celda(df_con_indice, fila="PROD-101", columna="Precio Unitario")
375
+ # Si existe:
376
+ precio = obtener_celda(df, fila="PROD-101", columna="Precio Unitario")
331
377
 
332
- # Caso 2: Sin cambiar índice (busca en la columna 'Codigo')
333
- precio = obtener_celda(df, fila="PROD-101", columna="Precio Unitario", columna_identificador="Codigo")
378
+ # Si NO existe, muestra feedback y devuelve None (sin dar error):
379
+ precio = obtener_celda(df, fila="PROD-999", columna="Precio Unitario")
380
+ # 👉 ⚠️ [obtener_celda] No se encontró la fila 'PROD-999' en el DataFrame. Retornando None.
334
381
  """
382
+ if df is None or df.empty:
383
+ if mostrar_feedback:
384
+ print(f"⚠️ [obtener_celda] El DataFrame está vacío o es None. Retornando {default}.")
385
+ if lanzar_error:
386
+ raise ValueError("El DataFrame está vacío o es None.")
387
+ return default
388
+
389
+ # Verificar columna
335
390
  if columna not in df.columns and columna != df.index.name:
336
- raise KeyError(f"La columna '{columna}' no existe en el DataFrame. Columnas disponibles: {list(df.columns)}")
391
+ if mostrar_feedback:
392
+ print(f"⚠️ [obtener_celda] La columna '{columna}' no existe. Columnas disponibles: {list(df.columns)}. Retornando {default}.")
393
+ if lanzar_error:
394
+ raise KeyError(f"La columna '{columna}' no existe en el DataFrame. Columnas disponibles: {list(df.columns)}")
395
+ return default
337
396
 
338
397
  # 1. Si la fila coincide directamente con el índice de pandas
339
398
  if fila in df.index:
@@ -344,15 +403,24 @@ def obtener_celda(
344
403
  coincidencias = df[df[columna_identificador] == fila]
345
404
  if not coincidencias.empty:
346
405
  return coincidencias.iloc[0][columna]
347
- raise KeyError(f"No se encontró ninguna fila con {columna_identificador}='{fila}'")
406
+ if mostrar_feedback:
407
+ print(f"⚠️ [obtener_celda] No se encontró ninguna fila con {columna_identificador}='{fila}'. Retornando {default}.")
408
+ if lanzar_error:
409
+ raise KeyError(f"No se encontró ninguna fila con {columna_identificador}='{fila}'")
410
+ return default
348
411
 
349
- # 3. Búsqueda automática en la primera columna o cualquier columna de texto
412
+ # 3. Búsqueda automática en la primera columna o cualquier columna
350
413
  for col in df.columns:
351
414
  coincidencias = df[df[col].astype(str) == str(fila)]
352
415
  if not coincidencias.empty:
353
416
  return coincidencias.iloc[0][columna]
354
417
 
355
- raise KeyError(f"No se encontró la fila identificada con '{fila}' en el DataFrame.")
418
+ if mostrar_feedback:
419
+ print(f"⚠️ [obtener_celda] No se encontró la fila identificada con '{fila}' en el DataFrame. Retornando {default}.")
420
+ if lanzar_error:
421
+ raise KeyError(f"No se encontró la fila identificada con '{fila}' en el DataFrame.")
422
+
423
+ return default
356
424
 
357
425
 
358
426
  def modificar_celda(
@@ -360,12 +428,29 @@ def modificar_celda(
360
428
  fila: Any,
361
429
  columna: str,
362
430
  nuevo_valor: Any,
363
- columna_identificador: Optional[str] = None
431
+ columna_identificador: Optional[str] = None,
432
+ lanzar_error: bool = False,
433
+ mostrar_feedback: bool = True
364
434
  ) -> pd.DataFrame:
365
435
  """
366
436
  Modifica el valor de una celda puntual buscando por nombre de fila y nombre de columna.
437
+ Si no se encuentra, muestra un mensaje de feedback y devuelve el DataFrame sin modificaciones.
367
438
  """
368
439
  df_out = df.copy()
440
+ if df_out.empty:
441
+ if mostrar_feedback:
442
+ print("⚠️ [modificar_celda] El DataFrame está vacío.")
443
+ if lanzar_error:
444
+ raise ValueError("El DataFrame está vacío.")
445
+ return df_out
446
+
447
+ if columna not in df_out.columns:
448
+ if mostrar_feedback:
449
+ print(f"⚠️ [modificar_celda] La columna '{columna}' no existe. Columnas disponibles: {list(df_out.columns)}")
450
+ if lanzar_error:
451
+ raise KeyError(f"La columna '{columna}' no existe.")
452
+ return df_out
453
+
369
454
  if fila in df_out.index:
370
455
  df_out.at[fila, columna] = nuevo_valor
371
456
  return df_out
@@ -376,7 +461,12 @@ def modificar_celda(
376
461
  df_out.at[idx[0], columna] = nuevo_valor
377
462
  return df_out
378
463
 
379
- raise KeyError(f"No se encontró la fila '{fila}' para modificar.")
464
+ if mostrar_feedback:
465
+ print(f"⚠️ [modificar_celda] No se encontró la fila '{fila}' para modificar. El DataFrame no fue alterado.")
466
+ if lanzar_error:
467
+ raise KeyError(f"No se encontró la fila '{fila}' para modificar.")
468
+
469
+ return df_out
380
470
 
381
471
 
382
472
  def buscar_v(
@@ -386,45 +476,33 @@ def buscar_v(
386
476
  columna_a_traer: str,
387
477
  clave_destino: Optional[str] = None,
388
478
  nombre_columna: Optional[str] = None,
389
- default: Any = np.nan
479
+ default: Any = np.nan,
480
+ lanzar_error: bool = False,
481
+ mostrar_feedback: bool = True
390
482
  ) -> Union[pd.Series, pd.DataFrame]:
391
483
  """
392
484
  Equivalente al BUSCARV / VLOOKUP de Excel para cruzar dos tablas en una sola línea.
393
-
394
- Parámetros:
395
- -----------
396
- df_origen : pd.DataFrame
397
- El DataFrame donde quieres insertar el nuevo valor (ej: df_ventas).
398
- df_destino : pd.DataFrame
399
- El DataFrame que contiene la tabla maestra con el dato buscado (ej: df_clientes).
400
- clave : str
401
- Nombre de la columna común en df_origen (ej: 'id_cliente').
402
- columna_a_traer : str
403
- Nombre de la columna que deseas extraer de df_destino (ej: 'nombre').
404
- clave_destino : str, opcional
405
- Nombre de la columna clave en df_destino si se llama distinto a 'clave'.
406
- nombre_columna : str, opcional
407
- Si se especifica, agrega la columna a df_origen y devuelve el DataFrame completo.
408
- Si es None, devuelve una pd.Series lista para asignar.
409
- default : Any (por defecto np.nan)
410
- Valor a colocar si no se encuentra coincidencia.
411
-
412
- Ejemplos:
413
- ---------
414
- # Forma 1: Asignación directa a una nueva columna
415
- df_ventas["Nombre_Cliente"] = buscar_v(df_ventas, df_clientes, clave="id_cliente", columna_a_traer="nombre")
416
-
417
- # Forma 2: Retornar DataFrame actualizado
418
- df_resultado = buscar_v(df_ventas, df_clientes, clave="id_cliente", columna_a_traer="nombre", nombre_columna="Cliente")
419
485
  """
420
486
  target_key = clave_destino or clave
421
-
487
+ errores = []
422
488
  if clave not in df_origen.columns:
423
- raise KeyError(f"La clave '{clave}' no existe en df_origen.")
489
+ errores.append(f"La clave '{clave}' no existe en df_origen (disponibles: {list(df_origen.columns)})")
424
490
  if target_key not in df_destino.columns:
425
- raise KeyError(f"La clave '{target_key}' no existe en df_destino.")
491
+ errores.append(f"La clave '{target_key}' no existe en df_destino (disponibles: {list(df_destino.columns)})")
426
492
  if columna_a_traer not in df_destino.columns:
427
- raise KeyError(f"La columna '{columna_a_traer}' no existe en df_destino.")
493
+ errores.append(f"La columna a traer '{columna_a_traer}' no existe en df_destino (disponibles: {list(df_destino.columns)})")
494
+
495
+ if errores:
496
+ if mostrar_feedback:
497
+ print(f"⚠️ [buscar_v] Error de configuración: {'; '.join(errores)}")
498
+ if lanzar_error:
499
+ raise KeyError("; ".join(errores))
500
+ serie_vacia = pd.Series([default] * len(df_origen), index=df_origen.index)
501
+ if nombre_columna:
502
+ df_out = df_origen.copy()
503
+ df_out[nombre_columna] = serie_vacia
504
+ return df_out
505
+ return serie_vacia
428
506
 
429
507
  # Mapeo rápido usando diccionario para máxima velocidad
430
508
  mapeo = df_destino.drop_duplicates(subset=[target_key]).set_index(target_key)[columna_a_traer].to_dict()
@@ -444,7 +522,9 @@ def conciliar_tablas(
444
522
  clave: str,
445
523
  columnas_comparar: Optional[List[str]] = None,
446
524
  sufijo_a: str = "_A",
447
- sufijo_b: str = "_B"
525
+ sufijo_b: str = "_B",
526
+ lanzar_error: bool = False,
527
+ mostrar_feedback: bool = True
448
528
  ) -> Dict[str, pd.DataFrame]:
449
529
  """
450
530
  Concilia y audita dos tablas (ej: sistema vs extracto bancario, o inventario teórico vs físico).
@@ -456,7 +536,17 @@ def conciliar_tablas(
456
536
  - 'solo_en_B': Filas que solo existen en la segunda tabla.
457
537
  """
458
538
  if clave not in df_a.columns or clave not in df_b.columns:
459
- raise KeyError(f"La columna clave '{clave}' debe existir en ambas tablas.")
539
+ msg = f"La columna clave '{clave}' debe existir en ambas tablas. (df_a: {list(df_a.columns)}, df_b: {list(df_b.columns)})"
540
+ if mostrar_feedback:
541
+ print(f"⚠️ [conciliar_tablas] {msg}")
542
+ if lanzar_error:
543
+ raise KeyError(msg)
544
+ return {
545
+ "coincidentes": pd.DataFrame(),
546
+ "diferencias": pd.DataFrame(),
547
+ "solo_en_A": df_a.copy() if df_a is not None else pd.DataFrame(),
548
+ "solo_en_B": df_b.copy() if df_b is not None else pd.DataFrame(),
549
+ }
460
550
 
461
551
  # Unir ambas tablas con outer join
462
552
  merged = pd.merge(df_a, df_b, on=clave, how='outer', suffixes=(sufijo_a, sufijo_b), indicator=True)
File without changes
File without changes
File without changes