---
name: read_xlsx
kind: function
lang: py
domain: infra
version: "1.0.0"
purity: impure
signature: "def read_xlsx(path: str, sheet: str = None, max_rows: int = None, header: bool = True) -> dict"
description: "Lee un archivo Excel (.xlsx) a estructuras en memoria con openpyxl (NO a markdown; complementa a excel_to_markdown). Espejo en lectura de write_xlsx_sheets: devuelve {status, sheets: {nombre: {headers: [...], rows: [[...]]}}}. Si sheet=None lee todas las hojas; si se indica, solo esa. Con header=True la primera fila de cada hoja son los headers. Maneja tipos de celda: fechas/datetime a ISO 8601, int/float, bool, None y formulas (valor calculado via data_only=True). Trunca por hoja a max_rows filas de datos si se indica. Impura: lee disco y NO lanza: en fallo devuelve {status: 'error', error}."
tags: [excel, xlsx, openpyxl, spreadsheet, office, onlyoffice, read, io, infra]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [openpyxl]
params:
  - name: path
    desc: "Ruta al archivo .xlsx a leer. Vacio o inexistente devuelve {status: 'error'} (no lanza). Se resuelve a ruta absoluta internamente."
  - name: sheet
    desc: "Nombre de la hoja a leer. None (default) lee TODAS las hojas del libro. Si se indica una hoja que no existe, devuelve {status: 'error'} con la lista de hojas disponibles."
  - name: max_rows
    desc: "Maximo de filas de DATOS a devolver por hoja (no cuenta la cabecera cuando header=True). None (default) = sin limite. Util para previsualizar libros grandes sin cargarlos enteros."
  - name: header
    desc: "Si True (default) la primera fila de cada hoja se interpreta como cabecera y va en 'headers'; el resto en 'rows'. Si False, 'headers' es [] y TODAS las filas (incluida la primera) van en 'rows'."
output: "Dict. En exito: {status: 'ok', sheets: {nombre_hoja: {headers: [...], rows: [[...], ...]}}}. En error: {status: 'error', error: '<mensaje>'}. Valores de celda como tipos nativos de Python: fechas/datetime como str ISO 8601, int/float, bool, str y None."
tested: true
tests: ["test_round_trip_escribe_lee_compara", "test_lee_solo_la_hoja_indicada", "test_max_rows_trunca_filas_de_datos", "test_header_false_no_consume_cabecera", "test_fecha_se_devuelve_como_iso", "test_formula_se_lee_como_valor_calculado", "test_archivo_inexistente_devuelve_error", "test_hoja_inexistente_devuelve_error", "test_path_vacio_devuelve_error"]
test_file_path: "python/functions/infra/read_xlsx_test.py"
file_path: "python/functions/infra/read_xlsx.py"
---

## Ejemplo

```python
import sys, os
sys.path.insert(0, os.path.join("python", "functions"))
from infra.read_xlsx import read_xlsx
from infra.write_xlsx_sheets import write_xlsx_sheets

# Escribe un libro y leelo de vuelta (round-trip)
write_xlsx_sheets("/tmp/ventas.xlsx", {
    "Ventas": [
        ["Producto", "Unidades", "Precio", "Activo"],
        ["Teclado", 12, 29.99, True],
        ["Raton", 30, 14.5, False],
        ["Monitor", None, 199.0, True],   # None -> None al leer
    ],
})

res = read_xlsx("/tmp/ventas.xlsx")
print(res["status"])                       # ok
print(list(res["sheets"].keys()))          # ['Ventas']
print(res["sheets"]["Ventas"]["headers"])  # ['Producto', 'Unidades', 'Precio', 'Activo']
print(res["sheets"]["Ventas"]["rows"][0])  # ['Teclado', 12, 29.99, True]

# Solo una hoja, primeras 1 fila de datos
res = read_xlsx("/tmp/ventas.xlsx", sheet="Ventas", max_rows=1)
print(res["sheets"]["Ventas"]["rows"])     # [['Teclado', 12, 29.99, True]]
```

## Cuando usarla

Usala cuando necesites los datos de un .xlsx como **estructuras de Python**
(listas y dicts) para procesarlos en codigo: validar, transformar, alimentar
otra funcion, hacer asserts. Es el espejo en lectura de `write_xlsx_sheets`
(mismo shape `{hoja: {headers, rows}}`) y la base para round-trips
escribir->leer. Si lo que quieres es una representacion **textual** del libro
para mostrar o resumir (p.ej. pasarla a un LLM), usa `excel_to_markdown_py_core`
en su lugar: aquella produce tablas markdown, esta produce datos crudos.

## Gotchas

- **Impura — lee de disco.** No lanza: devuelve `{"status": "error", ...}` ante
  archivo inexistente, hoja inexistente, path vacio o openpyxl ausente.
- **openpyxl carga el libro entero en memoria.** Aun en `read_only=True`, un
  libro muy grande consume RAM proporcional a su tamano; usa `max_rows` para
  previsualizar sin materializar todas las filas, pero recuerda que openpyxl
  igual abre el archivo completo.
- **`data_only=True`** devuelve el valor **cacheado** de las formulas, no la
  formula. Ese cache solo existe si un motor (Excel/LibreOffice) abrio y guardo
  el libro tras escribir la formula. openpyxl NO evalua formulas: un .xlsx con
  formulas escritas por openpyxl y nunca abierto en Excel devolvera `None` en
  esas celdas. Para round-trips fiables, escribe el VALOR, no la formula.
- **Requiere openpyxl** (ya instalado en `python/.venv`, version 3.1.5).
- **Tipos de celda**: None se conserva como None; int/float/str/bool nativos;
  `datetime.date` -> `"YYYY-MM-DD"`; `datetime.datetime` sin hora -> `"YYYY-MM-DD"`,
  con hora -> `"YYYY-MM-DDTHH:MM:SS"`. Cualquier otro tipo se serializa a str.
- **`header=False`** NO consume la primera fila: todas las filas (incluida la
  cabecera real, si la hubiera) van en `rows`. Util cuando el libro no tiene
  cabecera o quieres procesarla como dato.
- **Orden de hojas preservado** segun el orden del libro (igual que
  `write_xlsx_sheets` preserva el orden de insercion del dict).
