Excel y documentos de trabajo

Combinar hojas de Excel en una sola tabla con una columna de hoja de origen

Combina hojas de cálculo que utilizan las mismas columnas en una única tabla de Excel, registrando de qué hoja procede cada fila. Un pequeño libro sintético permite verificar manualmente el orden de las filas, los recuentos y los totales.

Ver el índice

La traducción se ha realizado con IA. Comprueba el código, las unidades y los valores junto con el original. La revisión por hablantes nativos de cada idioma aún no se ha completado. English

Para quién esEsta guía está dirigida a personas que reciben varias hojas de Excel con una estructura similar y necesitan un único conjunto de datos consolidado sin modificar el libro original.

Preparación
  • Python 3.12 y un comando de terminal que inicie esa versión.
  • openpyxl instalado con python -m pip install openpyxl.
  • Una carpeta de trabajo donde los scripts puedan crear carpetas dentro de outputs.
  • Las hojas que se van a combinar deben utilizar los mismos nombres de columna en el mismo orden.

01Definir exactamente qué se combinará

El flujo de trabajo combina hojas seleccionadas de un libro en un libro nuevo. Cada hoja de origen debe tener exactamente la misma fila de encabezados, incluido el orden de las columnas y el uso de mayúsculas y minúsculas. La salida añade source_sheet como primera columna para que cada registro conserve su procedencia.

Las filas en blanco se ignoran. Las filas no vacías se copian siguiendo el orden de las hojas y, dentro de cada hoja, su orden original. El libro de origen se abre en modo de solo lectura y el resultado se escribe en una carpeta independiente dentro de outputs.

02Crear un libro sintético

El libro que aparece a continuación es sintético y fue creado específicamente para este artículo. Guarda el script de preparación como create_excel_merge_demo.py. Crea tres hojas llamadas East, West y Online. Cada una utiliza las columnas order_id, customer y amount.

python
from pathlib import Path
from openpyxl import Workbook

SOURCE_DIR = Path("outputs") / "excel_merge_demo"
SOURCE = SOURCE_DIR / "regional_orders.xlsx"
HEADERS = ["order_id", "customer", "amount"]
DATA = {
    "East": [
        ["E001", "Ana", 120],
        ["E002", "Ben", 80],
    ],
    "West": [
        ["W001", "Cara", 150],
        ["W002", "Dan", 70],
    ],
    "Online": [
        ["O001", "Emi", 90],
        [None, None, None],
        ["O002", "Finn", 110],
    ],
}

SOURCE_DIR.parent.mkdir(parents=True, exist_ok=True)
SOURCE_DIR.mkdir()  # Stop if the synthetic source folder already exists.

workbook = Workbook()
for index, (sheet_name, rows) in enumerate(DATA.items()):
    if index == 0:
        sheet = workbook.active
        sheet.title = sheet_name
    else:
        sheet = workbook.create_sheet(sheet_name)
    sheet.append(HEADERS)
    for row in rows:
        sheet.append(row)

workbook.save(SOURCE)
text
python create_excel_merge_demo.py
HojaFilas de datos no vacíasTotal de amount
East2200
West2220
Online2200

Hay 6 registros de datos no vacíos en total. Sus valores de amount suman 620: 120 + 80 + 150 + 70 + 90 + 110. Online contiene deliberadamente una fila completamente vacía entre sus dos registros para demostrar que la lógica de combinación omite las filas en blanco.

03Determinar manualmente el orden esperado de las filas combinadas

El script enumera explícitamente las hojas como East, West y Online. Ese orden determina el orden de los registros combinados. Dentro de cada hoja, los registros mantienen su orden original de arriba abajo.

source_sheetorder_idcustomeramount
EastE001Ana120
EastE002Ben80
WestW001Cara150
WestW002Dan70
OnlineO001Emi90
OnlineO002Finn110

Por tanto, la salida tiene 4 columnas y 7 filas de hoja al incluir el encabezado: 1 fila de encabezado más 6 filas de datos. Los valores de source_sheet también sirven para una comprobación manual útil: East aparece dos veces, West aparece dos veces y Online aparece dos veces.

04Combinar las hojas en un libro nuevo

Guarda el siguiente script como excel_merge_sheets.py. Abre el libro de origen en modo de solo lectura, valida los nombres y encabezados de las hojas seleccionadas, ignora las filas completamente vacías y rechaza las celdas con fórmulas para evitar que las expresiones se copien silenciosamente fuera del contexto de su libro original.

python
from pathlib import Path
from openpyxl import Workbook, load_workbook

SOURCE = Path("outputs") / "excel_merge_demo" / "regional_orders.xlsx"
OUTPUT_DIR = Path("outputs") / "excel_merge_result"
OUTPUT = OUTPUT_DIR / "merged_orders.xlsx"
SHEETS = ("East", "West", "Online")
OUTPUT_SHEET = "Combined"
SOURCE_COLUMN = "source_sheet"


def read_sheet_rows(sheet, expected_header=None):
    first_row = next(
        sheet.iter_rows(min_row=1, max_row=1, values_only=True),
        None,
    )
    if first_row is None:
        raise ValueError(f"Sheet is empty: {sheet.title}")

    header = tuple(first_row)
    if any(not isinstance(name, str) or not name.strip() for name in header):
        raise ValueError(f"Invalid header in sheet: {sheet.title}")
    if len(set(header)) != len(header):
        raise ValueError(f"Duplicate header name in sheet: {sheet.title}")
    if SOURCE_COLUMN in header:
        raise ValueError(f"Reserved column already exists: {SOURCE_COLUMN}")
    if expected_header is not None and header != expected_header:
        raise ValueError(f"Header mismatch in sheet: {sheet.title}")

    rows = []
    for excel_row, values in enumerate(
        sheet.iter_rows(min_row=2, max_col=len(header), values_only=True),
        start=2,
    ):
        if all(value is None for value in values):
            continue
        if any(isinstance(value, str) and value.startswith("=") for value in values):
            raise ValueError(
                f"Formula found in {sheet.title} row {excel_row}; "
                "this example merges literal values only."
            )
        rows.append(tuple(values))

    return header, rows


def main() -> None:
    source_path = SOURCE.resolve(strict=True)
    if not source_path.is_file():
        raise ValueError("SOURCE must be an Excel file.")
    if len(set(SHEETS)) != len(SHEETS):
        raise ValueError("SHEETS contains a duplicate sheet name.")

    source_workbook = load_workbook(
        source_path,
        read_only=True,
        data_only=False,
    )
    try:
        missing = [name for name in SHEETS if name not in source_workbook.sheetnames]
        if missing:
            raise ValueError(f"Missing sheets: {missing}")

        expected_header = None
        combined_rows = []
        for sheet_name in SHEETS:
            sheet = source_workbook[sheet_name]
            header, rows = read_sheet_rows(sheet, expected_header)
            if expected_header is None:
                expected_header = header
            for row in rows:
                combined_rows.append((sheet_name, *row))
    finally:
        source_workbook.close()

    OUTPUT_DIR.parent.mkdir(parents=True, exist_ok=True)
    OUTPUT_DIR.mkdir()  # Refuse to reuse an existing output folder.

    output_workbook = Workbook()
    output_sheet = output_workbook.active
    output_sheet.title = OUTPUT_SHEET
    output_sheet.append([SOURCE_COLUMN, *expected_header])
    for row in combined_rows:
        output_sheet.append(row)
    output_workbook.save(OUTPUT)

    check_workbook = load_workbook(OUTPUT, read_only=True, data_only=False)
    try:
        if check_workbook.sheetnames != [OUTPUT_SHEET]:
            raise RuntimeError("Unexpected output worksheet structure.")
        check_sheet = check_workbook[OUTPUT_SHEET]
        written = list(check_sheet.iter_rows(values_only=True))
    finally:
        check_workbook.close()

    expected = [(SOURCE_COLUMN, *expected_header), *combined_rows]
    if written != expected:
        raise RuntimeError("Saved workbook does not match the expected rows.")

    print(f"Merged {len(SHEETS)} sheets into {len(combined_rows)} data rows.")
    print(f"Verified {len(expected_header) + 1} columns and {len(combined_rows)} data rows.")
    print(f"Output: {OUTPUT.as_posix()}")


if __name__ == "__main__":
    main()
text
python excel_merge_sheets.py

05Comparar el resultado esperado

Para el libro sintético, la salida debería contener exactamente una hoja llamada Combined. Su primera fila debe ser source_sheet, order_id, customer, amount, seguida de los 6 registros calculados anteriormente.

La salida de consola esperada que aparece a continuación se obtuvo manualmente a partir del código y los datos sintéticos. No es un registro capturado de una ejecución.

text
Merged 3 sheets into 6 data rows.
Verified 4 columns and 6 data rows.
Output: outputs/excel_merge_result/merged_orders.xlsx
ComprobaciónResultado esperado
Hojas de origen seleccionadas3
Filas de datos combinadas6
Columnas de salida4
Filas de East2
Filas de West2
Filas de Online2
Total de amount620

06Comprobar el libro antes de utilizar datos reales

  1. Abre merged_orders.xlsx y confirma que haya una sola hoja llamada Combined.
  2. Confirma que source_sheet sea la primera columna y que las tres columnas originales aparezcan después en el mismo orden.
  3. Cuenta los registros por source_sheet. East, West y Online deberían aparecer exactamente dos veces cada uno.
  4. Suma manualmente la columna amount o hazlo con Excel. El total esperado es 620.
  5. Confirma que la fila en blanco de Online no se haya convertido en un registro vacío en la salida combinada.
  6. Ejecuta el script otra vez sin cambiar OUTPUT_DIR. Debería detenerse con FileExistsError en lugar de sustituir el resultado anterior.

En libros reales, modifica SHEETS explícitamente en lugar de combinar automáticamente todas las hojas. Esto reduce la posibilidad de incluir accidentalmente tablas de consulta, notas, hojas ocultas o hojas de resumen que estén almacenadas en el mismo archivo.

07Reconocer errores comunes

SíntomaQué comprobar
ModuleNotFoundError: No module named openpyxlInstala openpyxl en el mismo entorno de Python con python -m pip install openpyxl.
FileNotFoundErrorComprueba SOURCE y el directorio de trabajo de la terminal. Para este ejemplo, ejecuta primero la preparación del libro sintético.
Missing sheetsComprueba exactamente los nombres indicados en SHEETS, incluidos los espacios y el uso de mayúsculas y minúsculas.
Header mismatchVerifica que todas las hojas seleccionadas utilicen nombres de columna idénticos en el mismo orden.
Reserved column already existsCambia el nombre de la columna de origen source_sheet o modifica SOURCE_COLUMN por un nombre que no entre en conflicto.
Formula foundDecide cómo deben tratarse las fórmulas antes de combinar. Este tutorial se detiene deliberadamente en lugar de copiar expresiones de fórmulas sin contexto.
FileExistsErrorLa carpeta de salida ya existe. Revisa el resultado anterior y utiliza un destino nuevo en lugar de sobrescribirlo.

Si el guardado o la verificación fallan después de crear OUTPUT_DIR, la carpeta o el libro pueden quedar como un resultado parcial. Considéralos no verificados y utiliza un destino nuevo después de corregir el problema.

08Comprender los límites

El script está pensado para hojas de datos rectangulares con una única fila de encabezados. No maneja encabezados de varias filas, celdas de encabezado combinadas, tablas dinámicas, gráficos, imágenes ni hojas donde varias tablas no relacionadas compartan la misma hoja.

Solo se consolidan los valores de las celdas. No se reproducen formatos numéricos, fuentes, rellenos, formato condicional, reglas de validación, hipervínculos, comentarios, fórmulas, alturas de fila, anchos de columna ni configuraciones de nivel de hoja. Los valores literales de fecha y numéricos pueden conservarse como valores, pero su formato de visualización original no se copia.

El script también carga todas las tuplas de filas combinadas en la memoria de Python antes de crear el libro de salida. Esto resulta cómodo para este pequeño flujo de trabajo verificado, pero puede no ser adecuado para libros muy grandes. En archivos grandes, un diseño de streaming con salida write-only y validación incremental independiente reduciría el uso de memoria.

Registro de ejecución y verificación

2026-09-20 · ejemplo revisado manualmente · objetivo: Python 3.12 · se requiere openpyxl · sin ejecución

  • Se contaron manualmente 2 registros no vacíos en East, 2 en West y 2 en Online, para un total de 6 registros combinados.
  • Se calcularon manualmente los totales de amount de cada hoja como 200, 220 y 200, dando un total general de 620.
  • Se obtuvo manualmente el orden esperado de salida: primero las filas de East, después West y luego Online, omitiendo la fila completamente vacía de Online.
  • Se revisó el código para confirmar que el libro de origen se abre en modo de solo lectura y el resultado se escribe en una carpeta outputs independiente.
  • Se revisaron las comprobaciones de encabezados, la comprobación reservada de source_sheet, el tratamiento de filas en blanco, el rechazo de fórmulas y la comparación de filas después de guardar.
  • Se obtuvieron manualmente las 4 columnas de salida, las 6 filas de datos y los mensajes esperados de consola.
Límites de la verificación
  • El código no fue ejecutado por el autor de esta respuesta; aquí no se crearon ni abrieron archivos XLSX.
  • No se probaron libros con fórmulas, celdas combinadas, formato, fechas, hipervínculos, hojas ocultas, archivos dañados ni libros muy grandes.
  • El comportamiento de openpyxl con el libro sintético se dedujo a partir del código, pero no se confirmó en un entorno de ejecución de Python.
  • Las URL de la documentación oficial se proporcionaron a partir de ubicaciones de documentación conocidas, pero no se comprobaron en línea.

Criterios de redacción y verificación de todo el sitio

Fuentes de referencia

Las explicaciones y los ejemplos son de elaboración propia. Puedes consultar los comportamientos y conceptos relacionados en las siguientes fuentes oficiales.