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.
- {tablas_python-0.1.1 → tablas_python-0.1.2}/PKG-INFO +1 -1
- {tablas_python-0.1.1 → tablas_python-0.1.2}/helpers/display_helper.py +66 -19
- {tablas_python-0.1.1 → tablas_python-0.1.2}/pyproject.toml +1 -1
- {tablas_python-0.1.1 → tablas_python-0.1.2}/tablas_python/__init__.py +1 -1
- {tablas_python-0.1.1 → tablas_python-0.1.2}/tablas_python.egg-info/PKG-INFO +1 -1
- {tablas_python-0.1.1 → tablas_python-0.1.2}/utils/data_helpers.py +141 -51
- {tablas_python-0.1.1 → tablas_python-0.1.2}/LICENSE +0 -0
- {tablas_python-0.1.1 → tablas_python-0.1.2}/README.md +0 -0
- {tablas_python-0.1.1 → tablas_python-0.1.2}/helpers/__init__.py +0 -0
- {tablas_python-0.1.1 → tablas_python-0.1.2}/helpers/table_manager.py +0 -0
- {tablas_python-0.1.1 → tablas_python-0.1.2}/setup.cfg +0 -0
- {tablas_python-0.1.1 → tablas_python-0.1.2}/tablas_python/cli.py +0 -0
- {tablas_python-0.1.1 → tablas_python-0.1.2}/tablas_python.egg-info/SOURCES.txt +0 -0
- {tablas_python-0.1.1 → tablas_python-0.1.2}/tablas_python.egg-info/dependency_links.txt +0 -0
- {tablas_python-0.1.1 → tablas_python-0.1.2}/tablas_python.egg-info/entry_points.txt +0 -0
- {tablas_python-0.1.1 → tablas_python-0.1.2}/tablas_python.egg-info/requires.txt +0 -0
- {tablas_python-0.1.1 → tablas_python-0.1.2}/tablas_python.egg-info/top_level.txt +0 -0
- {tablas_python-0.1.1 → tablas_python-0.1.2}/utils/__init__.py +0 -0
- {tablas_python-0.1.1 → tablas_python-0.1.2}/utils/batch_processor.py +0 -0
- {tablas_python-0.1.1 → tablas_python-0.1.2}/utils/excel_extractor.py +0 -0
- {tablas_python-0.1.1 → tablas_python-0.1.2}/utils/excel_writer.py +0 -0
- {tablas_python-0.1.1 → tablas_python-0.1.2}/utils/exporter.py +0 -0
- {tablas_python-0.1.1 → tablas_python-0.1.2}/utils/file_utils.py +0 -0
- {tablas_python-0.1.1 → tablas_python-0.1.2}/utils/pdf_extractor.py +0 -0
- {tablas_python-0.1.1 → tablas_python-0.1.2}/utils/sqlite_extractor.py +0 -0
- {tablas_python-0.1.1 → tablas_python-0.1.2}/utils/table_cleaner.py +0 -0
- {tablas_python-0.1.1 → tablas_python-0.1.2}/utils/validator.py +0 -0
|
@@ -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(
|
|
119
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
131
|
-
console.print(f"[dim]... y {
|
|
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(
|
|
135
|
-
|
|
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(
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
df
|
|
145
|
-
|
|
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.
|
|
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 = [
|
|
@@ -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
|
-
|
|
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
|
-
|
|
325
|
-
|
|
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
|
-
#
|
|
330
|
-
precio = obtener_celda(
|
|
375
|
+
# Si existe:
|
|
376
|
+
precio = obtener_celda(df, fila="PROD-101", columna="Precio Unitario")
|
|
331
377
|
|
|
332
|
-
#
|
|
333
|
-
precio = obtener_celda(df, fila="PROD-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|