diff --git a/README.md b/README.md
index d22c4c3..8e2ceb7 100644
--- a/README.md
+++ b/README.md
@@ -74,11 +74,11 @@ Detailed explanations:
## Current status
-The project foundation and **Phases 1–8 are complete**. **Phase 9: External Libraries is now in progress** with its first reviewed-track chapter, [`pandas`](external-libraries/01-pandas/README.md), targeting pandas 3.0.x.
+The project foundation and **Phases 1–8 are complete**. **Phase 9: External Libraries is in progress** with two reviewed-track chapters: [`pandas`](external-libraries/01-pandas/README.md), targeting pandas 3.0.x, and [`openpyxl`](external-libraries/02-openpyxl/README.md), targeting openpyxl 3.1.x.
-The new phase adds an explicit third-party dependency contract through [`requirements-external.txt`](requirements-external.txt), and repository CI installs those dependencies before running approved examples. The pandas chapter covers labeled tabular data, indexes and alignment, dtypes, selection, pandas 3.0 Copy-on-Write, missing-data policy, vectorized transformations, grouping, validated joins, reshaping, and deterministic CSV pipelines.
+The phase now covers both tabular-data transformation and Excel workbook automation. Repository CI installs the explicit third-party contract in [`requirements-external.txt`](requirements-external.txt) before running approved examples.
-The next planned Phase 9 chapters are `openpyxl`, `requests`, and `pytest`. See the [External Libraries index](external-libraries/README.md), [roadmap](docs/roadmap.en.md), or [full learning path](docs/learning-path.en.md) for current status.
+The next planned Phase 9 chapters are `requests` and `pytest`. See the [External Libraries index](external-libraries/README.md), [roadmap](docs/roadmap.en.md), or [full learning path](docs/learning-path.en.md) for current status.
## Visual identity
diff --git a/docs/learning-path.en.md b/docs/learning-path.en.md
index d0e8f81..f7f8b1b 100644
--- a/docs/learning-path.en.md
+++ b/docs/learning-path.en.md
@@ -120,11 +120,11 @@ Phase 8 is complete with nine reviewed chapters. The sequence moves from path mo
[Open the External Libraries section index](../external-libraries/README.md)
1. ✅ [`pandas`: Working with Tabular Data](../external-libraries/01-pandas/README.md)
-2. ⏳ `openpyxl`
+2. ✅ [`openpyxl`: Automating Excel Workbooks](../external-libraries/02-openpyxl/README.md)
3. ⏳ `requests`
4. ⏳ `pytest`
-Phase 9 is in progress. Chapter 01 establishes third-party dependency contracts and introduces labeled/tabular data with pandas 3.0.x: `Series`, `DataFrame`, indexes, label alignment, dtypes, selection, Copy-on-Write, missing data, vectorized transformations, grouping, validated joins, reshaping, and deterministic CSV pipelines.
+Phase 9 is in progress. Chapter 01 establishes labeled/tabular data contracts with pandas 3.0.x. Chapter 02 adds openpyxl 3.1.x workbook automation: worksheet/cell access, formulas versus cached values, styles, tables, validations, optimized read/write modes, VBA preservation boundaries, round-trip risks, and deterministic workbook verification.
## Phase 10 · Practical Projects ⏳
diff --git a/docs/learning-path.es.md b/docs/learning-path.es.md
index f151572..4da9102 100644
--- a/docs/learning-path.es.md
+++ b/docs/learning-path.es.md
@@ -120,11 +120,11 @@ La Fase 8 está completada con nueve capítulos revisados. La secuencia avanza d
[Abre el índice de la sección Bibliotecas Externas](../external-libraries/README.es.md)
1. ✅ [`pandas`: Trabajando con Datos Tabulares](../external-libraries/01-pandas/README.es.md)
-2. ⏳ `openpyxl`
+2. ✅ [`openpyxl`: Automatizando Libros de Excel](../external-libraries/02-openpyxl/README.es.md)
3. ⏳ `requests`
4. ⏳ `pytest`
-La Fase 9 está en progreso. El Capítulo 01 establece contratos de dependencias de terceros e introduce datos etiquetados/tabulares con pandas 3.0.x: `Series`, `DataFrame`, índices, alineación por labels, dtypes, selección, Copy-on-Write, datos ausentes, transformaciones vectorizadas, agrupaciones, joins validados, reshape y pipelines CSV deterministas.
+La Fase 9 está en progreso. El Capítulo 01 establece contratos de datos etiquetados/tabulares con pandas 3.0.x. El Capítulo 02 añade automatización de libros con openpyxl 3.1.x: acceso a hojas/celdas, fórmulas frente a valores en caché, estilos, tablas, validaciones, modos optimizados de lectura/escritura, límites de preservación de VBA, riesgos de round-trip y verificación determinista del libro.
## Fase 10 · Proyectos Prácticos ⏳
diff --git a/docs/learning-path.pt-BR.md b/docs/learning-path.pt-BR.md
index 6e7f0cf..47b4eee 100644
--- a/docs/learning-path.pt-BR.md
+++ b/docs/learning-path.pt-BR.md
@@ -120,11 +120,11 @@ A Fase 8 está concluída com nove capítulos revisados. A sequência avança de
[Abra o índice da seção Bibliotecas Externas](../external-libraries/README.pt-BR.md)
1. ✅ [`pandas`: Trabalhando com Dados Tabulares](../external-libraries/01-pandas/README.pt-BR.md)
-2. ⏳ `openpyxl`
+2. ✅ [`openpyxl`: Automatizando Workbooks do Excel](../external-libraries/02-openpyxl/README.pt-BR.md)
3. ⏳ `requests`
4. ⏳ `pytest`
-A Fase 9 está em andamento. O Capítulo 01 estabelece contratos de dependências de terceiros e introduz dados rotulados/tabulares com pandas 3.0.x: `Series`, `DataFrame`, índices, alinhamento por labels, dtypes, seleção, Copy-on-Write, dados ausentes, transformações vetorizadas, agrupamentos, joins validados, reshape e pipelines CSV determinísticos.
+A Fase 9 está em andamento. O Capítulo 01 estabelece contratos de dados rotulados/tabulares com pandas 3.0.x. O Capítulo 02 acrescenta automação de workbooks com openpyxl 3.1.x: acesso a planilhas/células, fórmulas versus valores em cache, estilos, tabelas, validações, modos otimizados de leitura/escrita, limites de preservação de VBA, riscos de round-trip e verificação determinística do workbook.
## Fase 10 · Projetos Práticos ⏳
diff --git a/docs/localized/README.es.md b/docs/localized/README.es.md
index da97877..cd19d4a 100644
--- a/docs/localized/README.es.md
+++ b/docs/localized/README.es.md
@@ -74,11 +74,11 @@ Explicaciones detalladas:
## Estado actual
-La base del proyecto y las **Fases 1–8 están completadas**. La **Fase 9: Bibliotecas Externas está ahora en progreso** con su primer capítulo de la ruta, [`pandas`](../../external-libraries/01-pandas/README.es.md), apuntando a pandas 3.0.x.
+La base del proyecto y las **Fases 1–8 están completadas**. La **Fase 9: Bibliotecas Externas está en progreso** con dos capítulos revisados de la ruta: [`pandas`](../../external-libraries/01-pandas/README.es.md), apuntando a pandas 3.0.x, y [`openpyxl`](../../external-libraries/02-openpyxl/README.es.md), apuntando a openpyxl 3.1.x.
-La nueva fase añade un contrato explícito de dependencias de terceros mediante [`requirements-external.txt`](../../requirements-external.txt), y el CI del repositorio instala esas dependencias antes de ejecutar los ejemplos aprobados. El capítulo de pandas cubre datos tabulares etiquetados, índices y alineación, dtypes, selección, Copy-on-Write de pandas 3.0, política de datos ausentes, transformaciones vectorizadas, agrupaciones, joins validados, reshape y pipelines CSV deterministas.
+La fase ahora cubre tanto transformación de datos tabulares como automatización de libros de Excel. El CI del repositorio instala el contrato explícito de terceros en [`requirements-external.txt`](../../requirements-external.txt) antes de ejecutar los ejemplos aprobados.
-Los siguientes capítulos planificados de la Fase 9 son `openpyxl`, `requests` y `pytest`. Consulta el [índice de Bibliotecas Externas](../../external-libraries/README.es.md), el [roadmap](../roadmap.es.md) o la [ruta completa de aprendizaje](../learning-path.es.md) para el estado actualizado.
+Los siguientes capítulos planificados de la Fase 9 son `requests` y `pytest`. Consulta el [índice de Bibliotecas Externas](../../external-libraries/README.es.md), el [roadmap](../roadmap.es.md) o la [ruta completa de aprendizaje](../learning-path.es.md) para el estado actualizado.
## Identidad visual
diff --git a/docs/localized/README.pt-BR.md b/docs/localized/README.pt-BR.md
index 88bdcbf..483bdbd 100644
--- a/docs/localized/README.pt-BR.md
+++ b/docs/localized/README.pt-BR.md
@@ -74,11 +74,11 @@ Explicações detalhadas:
## Status atual
-A fundação do projeto e as **Fases 1–8 estão concluídas**. A **Fase 9: Bibliotecas Externas agora está em andamento** com seu primeiro capítulo da trilha, [`pandas`](../../external-libraries/01-pandas/README.pt-BR.md), tendo pandas 3.0.x como alvo.
+A fundação do projeto e as **Fases 1–8 estão concluídas**. A **Fase 9: Bibliotecas Externas está em andamento** com dois capítulos revisados da trilha: [`pandas`](../../external-libraries/01-pandas/README.pt-BR.md), tendo pandas 3.0.x como alvo, e [`openpyxl`](../../external-libraries/02-openpyxl/README.pt-BR.md), tendo openpyxl 3.1.x como alvo.
-A nova fase acrescenta um contrato explícito de dependências de terceiros por meio de [`requirements-external.txt`](../../requirements-external.txt), e o CI do repositório instala essas dependências antes de executar os exemplos aprovados. O capítulo de pandas cobre dados tabulares rotulados, índices e alinhamento, dtypes, seleção, Copy-on-Write do pandas 3.0, política de dados ausentes, transformações vetorizadas, agrupamentos, joins validados, reshape e pipelines CSV determinísticos.
+A fase agora cobre tanto transformação de dados tabulares quanto automação de workbooks do Excel. O CI do repositório instala o contrato explícito de terceiros em [`requirements-external.txt`](../../requirements-external.txt) antes de executar os exemplos aprovados.
-Os próximos capítulos planejados da Fase 9 são `openpyxl`, `requests` e `pytest`. Consulte o [índice de Bibliotecas Externas](../../external-libraries/README.pt-BR.md), o [roadmap](../roadmap.pt-BR.md) ou a [trilha completa de estudos](../learning-path.pt-BR.md) para o status atualizado.
+Os próximos capítulos planejados da Fase 9 são `requests` e `pytest`. Consulte o [índice de Bibliotecas Externas](../../external-libraries/README.pt-BR.md), o [roadmap](../roadmap.pt-BR.md) ou a [trilha completa de estudos](../learning-path.pt-BR.md) para o status atualizado.
## Identidade visual
diff --git a/docs/project-structure.en.md b/docs/project-structure.en.md
index a8bf9be..c966eb7 100644
--- a/docs/project-structure.en.md
+++ b/docs/project-structure.en.md
@@ -193,16 +193,26 @@ python-study-guide/
│ ├── README.md
│ ├── README.pt-BR.md
│ ├── README.es.md
-│ └── 01-pandas/
+│ ├── 01-pandas/
+│ │ ├── README.md
+│ │ ├── README.pt-BR.md
+│ │ ├── README.es.md
+│ │ └── examples/
+│ │ ├── csv_pipeline.py
+│ │ ├── dataframe_basics.py
+│ │ ├── filter_and_assign.py
+│ │ ├── groupby_summary.py
+│ │ └── merge_tables.py
+│ └── 02-openpyxl/
│ ├── README.md
│ ├── README.pt-BR.md
│ ├── README.es.md
│ └── examples/
-│ ├── csv_pipeline.py
-│ ├── dataframe_basics.py
-│ ├── filter_and_assign.py
-│ ├── groupby_summary.py
-│ └── merge_tables.py
+│ ├── load_and_iterate.py
+│ ├── styled_report.py
+│ ├── table_and_validation.py
+│ ├── workbook_basics.py
+│ └── write_only_export.py
├── functions/
│ ├── README.md
│ ├── README.pt-BR.md
@@ -538,7 +548,7 @@ python-study-guide/
- `docs/`: master learning paths, roadmaps, project architecture, localized project documents, policies, and responsible AI-assisted development guidance.
- `errors-files-and-modules/`: complete Phase 7 learning path. Chapters 01–05 cover runtime exception handling, deliberate raising and custom exceptions, safe text-file I/O with `open()` and `with`, TXT/CSV/JSON parsing and writing, and code organization through imports, modules, regular packages, execution context, and dependency design, in English, Brazilian Portuguese, and Spanish with deterministic executable examples.
- `exercises/`: focused practice activities connected to learning chapters.
-- `external-libraries/`: Phase 9 learning path for third-party packages. It currently contains the multilingual pandas 3.0.x chapter and five deterministic executable examples, with `openpyxl`, `requests`, and `pytest` planned next.
+- `external-libraries/`: Phase 9 learning path for third-party packages. It currently contains reviewed multilingual chapters for pandas 3.0.x and openpyxl 3.1.x with ten deterministic executable examples in total; `requests` and `pytest` are planned next.
- `functions/`: complete Phase 5 learning path. Chapters 01–09 cover defining and calling functions, required inputs, returned values, scope and name lookup, type hints for function interfaces, default values including definition-time evaluation and mutable-default safety, variable-length positional and keyword argument collection with `*args` and `**kwargs`, composition through helper and coordinating functions with explicit dependencies and simple call graphs, and explicit data-flow tracing across calls including parameter bindings, rebinding versus mutation, `None`, tuple results, and return-based handoffs, in English, Brazilian Portuguese, and Spanish with deterministic executable examples.
- `fundamentals/`: complete Phase 1 learning path. Its six chapters teach how Python runs a program, how to use `print()` and `input()`, how assignment and naming work, how to recognize and inspect common built-in data types, and how to convert compatible values deliberately, with aligned multilingual explanations and executable examples.
- `practical-projects/`: future small projects combining several concepts.
diff --git a/docs/project-structure.es.md b/docs/project-structure.es.md
index 24b205f..b4276a9 100644
--- a/docs/project-structure.es.md
+++ b/docs/project-structure.es.md
@@ -193,16 +193,26 @@ python-study-guide/
│ ├── README.md
│ ├── README.pt-BR.md
│ ├── README.es.md
-│ └── 01-pandas/
+│ ├── 01-pandas/
+│ │ ├── README.md
+│ │ ├── README.pt-BR.md
+│ │ ├── README.es.md
+│ │ └── examples/
+│ │ ├── csv_pipeline.py
+│ │ ├── dataframe_basics.py
+│ │ ├── filter_and_assign.py
+│ │ ├── groupby_summary.py
+│ │ └── merge_tables.py
+│ └── 02-openpyxl/
│ ├── README.md
│ ├── README.pt-BR.md
│ ├── README.es.md
│ └── examples/
-│ ├── csv_pipeline.py
-│ ├── dataframe_basics.py
-│ ├── filter_and_assign.py
-│ ├── groupby_summary.py
-│ └── merge_tables.py
+│ ├── load_and_iterate.py
+│ ├── styled_report.py
+│ ├── table_and_validation.py
+│ ├── workbook_basics.py
+│ └── write_only_export.py
├── functions/
│ ├── README.md
│ ├── README.pt-BR.md
@@ -538,7 +548,7 @@ python-study-guide/
- `docs/`: rutas completas de aprendizaje, roadmaps, arquitectura del proyecto, documentos localizados, políticas y guía de desarrollo responsable asistido por IA.
- `errors-files-and-modules/`: ruta completa de la Fase 7. Los Capítulos 01–05 cubren manejo de excepciones en runtime, lanzamiento deliberado y excepciones personalizadas, I/O seguro de archivos de texto con `open()` y `with`, parsing y escritura de TXT/CSV/JSON y organización del código mediante imports, módulos, paquetes regulares, contexto de ejecución y diseño de dependencias, en inglés, portugués de Brasil y español con ejemplos ejecutables deterministas.
- `exercises/`: actividades prácticas relacionadas con los capítulos.
-- `external-libraries/`: ruta de la Fase 9 para paquetes de terceros. Actualmente contiene el capítulo multilingüe de pandas 3.0.x y cinco ejemplos ejecutables deterministas, con `openpyxl`, `requests` y `pytest` planificados a continuación.
+- `external-libraries/`: ruta de la Fase 9 para paquetes de terceros. Actualmente contiene capítulos multilingües revisados de pandas 3.0.x y openpyxl 3.1.x con diez ejemplos ejecutables deterministas en total; `requests` y `pytest` son los siguientes planificados.
- `functions/`: ruta completa de la Fase 5. Los Capítulos 01–09 cubren definición y llamada de funciones, entradas obligatorias, valores retornados, alcance y búsqueda de nombres, type hints para interfaces de funciones, valores predeterminados incluida la evaluación al definir la función y la seguridad con valores mutables, recolección de argumentos posicionales y por palabra clave de cantidad variable con `*args` y `**kwargs`, composición mediante funciones auxiliares y coordinadoras con dependencias explícitas y grafos simples de llamadas, y seguimiento explícito del flujo de datos entre llamadas, incluidos vínculos de parámetros, reasignación frente a mutación, `None`, resultados en tupla y traspasos mediante `return`, en inglés, portugués de Brasil y español, con ejemplos ejecutables determinísticos.
- `fundamentals/`: ruta completa de la Fase 1. Sus seis capítulos enseñan cómo Python ejecuta un programa, cómo usar `print()` e `input()`, cómo funcionan la asignación y los nombres, cómo reconocer e inspeccionar tipos de datos incorporados comunes y cómo convertir valores compatibles de forma deliberada, con explicaciones multilingües alineadas y ejemplos ejecutables.
- `practical-projects/`: futuros proyectos pequeños que combinarán varios conceptos.
diff --git a/docs/project-structure.pt-BR.md b/docs/project-structure.pt-BR.md
index b714643..de0891d 100644
--- a/docs/project-structure.pt-BR.md
+++ b/docs/project-structure.pt-BR.md
@@ -193,16 +193,26 @@ python-study-guide/
│ ├── README.md
│ ├── README.pt-BR.md
│ ├── README.es.md
-│ └── 01-pandas/
+│ ├── 01-pandas/
+│ │ ├── README.md
+│ │ ├── README.pt-BR.md
+│ │ ├── README.es.md
+│ │ └── examples/
+│ │ ├── csv_pipeline.py
+│ │ ├── dataframe_basics.py
+│ │ ├── filter_and_assign.py
+│ │ ├── groupby_summary.py
+│ │ └── merge_tables.py
+│ └── 02-openpyxl/
│ ├── README.md
│ ├── README.pt-BR.md
│ ├── README.es.md
│ └── examples/
-│ ├── csv_pipeline.py
-│ ├── dataframe_basics.py
-│ ├── filter_and_assign.py
-│ ├── groupby_summary.py
-│ └── merge_tables.py
+│ ├── load_and_iterate.py
+│ ├── styled_report.py
+│ ├── table_and_validation.py
+│ ├── workbook_basics.py
+│ └── write_only_export.py
├── functions/
│ ├── README.md
│ ├── README.pt-BR.md
@@ -538,7 +548,7 @@ python-study-guide/
- `docs/`: trilhas completas de estudos, roadmaps, arquitetura do projeto, documentos localizados, políticas e guia de desenvolvimento responsável assistido por IA.
- `errors-files-and-modules/`: trilha completa da Fase 7. Os Capítulos 01–05 cobrem tratamento de exceções em runtime, levantamento deliberado e exceções personalizadas, I/O seguro de arquivos de texto com `open()` e `with`, parsing e escrita de TXT/CSV/JSON e organização do código por imports, módulos, pacotes regulares, contexto de execução e design de dependências, em inglês, português brasileiro e espanhol com exemplos executáveis determinísticos.
- `exercises/`: atividades práticas relacionadas aos capítulos.
-- `external-libraries/`: trilha da Fase 9 para pacotes de terceiros. Atualmente contém o capítulo multilíngue de pandas 3.0.x e cinco exemplos executáveis determinísticos, com `openpyxl`, `requests` e `pytest` planejados na sequência.
+- `external-libraries/`: trilha da Fase 9 para pacotes de terceiros. Atualmente contém capítulos multilíngues revisados de pandas 3.0.x e openpyxl 3.1.x com dez exemplos executáveis determinísticos no total; `requests` e `pytest` são os próximos planejados.
- `functions/`: trilha completa da Fase 5. Os Capítulos 01–09 cobrem definição e chamada de funções, entradas obrigatórias, valores retornados, escopo e busca de nomes, type hints para interfaces de funções, valores padrão incluindo avaliação no momento da definição e segurança com padrões mutáveis, coleta de argumentos posicionais e nomeados de quantidade variável com `*args` e `**kwargs`, composição por funções auxiliares e coordenadoras com dependências explícitas e grafos simples de chamadas e rastreamento explícito do fluxo de dados entre chamadas, incluindo vínculos de parâmetros, reatribuição versus mutação, `None`, resultados em tupla e passagens por `return`, em inglês, português brasileiro e espanhol com exemplos executáveis determinísticos.
- `fundamentals/`: trilha completa da Fase 1. Seus seis capítulos ensinam como o Python executa um programa, como usar `print()` e `input()`, como funcionam atribuição e nomes, como reconhecer e inspecionar tipos de dados embutidos comuns e como converter valores compatíveis de forma deliberada, com explicações multilíngues alinhadas e exemplos executáveis.
- `practical-projects/`: futuros projetos pequenos combinando diversos conceitos.
diff --git a/docs/roadmap.en.md b/docs/roadmap.en.md
index ad88bbc..a593493 100644
--- a/docs/roadmap.en.md
+++ b/docs/roadmap.en.md
@@ -23,10 +23,10 @@ This roadmap tracks both the educational curriculum and the repository foundatio
| 6. Comments, documentation, and clean code | Complete | Six reviewed chapters are available and the pilot educational section is officially complete |
| 7. Errors, files, and modules | Complete | Five reviewed chapters cover exception handling, deliberate exception signaling, safe file I/O, TXT/CSV/JSON data formats, and imports/modules/packages |
| 8. Standard library | Complete | Nine reviewed chapters cover paths, date/time, JSON, CSV, logging, specialized collections, lazy iteration, decimal arithmetic, and OS/filesystem operations |
-| 9. External libraries | In progress | pandas 3.0.x chapter available with an explicit third-party dependency contract and deterministic examples |
+| 9. External libraries | In progress | pandas 3.0.x and openpyxl 3.1.x chapters available with explicit dependency contracts and deterministic examples |
| 10. Practical projects | Planned | Curriculum not started |
-Phases 0–8 are complete. Phase 9: External Libraries is now in progress with `pandas` as its first chapter. The phase introduces explicit third-party dependency contracts through `requirements-external.txt`, while `openpyxl`, `requests`, and `pytest` remain planned.
+Phases 0–8 are complete. Phase 9: External Libraries is in progress with `pandas` and `openpyxl` available. The phase now covers tabular-data transformation and Excel workbook automation through explicit third-party dependency contracts, while `requests` and `pytest` remain planned.
## Phase 0: Project foundation
@@ -157,11 +157,11 @@ Phase 8 is complete. Chapters 01–08 build contracts for paths, date/time, stru
See the [section learning path](../external-libraries/README.md).
- [x] [`pandas`](../external-libraries/01-pandas/README.md)
-- [ ] `openpyxl`
+- [x] [`openpyxl`](../external-libraries/02-openpyxl/README.md)
- [ ] `requests`
- [ ] `pytest`
-Phase 9 is in progress. Chapter 01 introduces pandas 3.0.x for labeled tabular data, selection, Copy-on-Write, missing-data policy, vectorized transformations, grouping, validated joins, reshaping, and CSV pipelines. Executable external-library examples use the dependency contract declared in [`requirements-external.txt`](../requirements-external.txt).
+Phase 9 is in progress. Chapter 01 introduces pandas 3.0.x for labeled tabular data, selection, Copy-on-Write, missing-data policy, vectorized transformations, grouping, validated joins, reshaping, and CSV pipelines. Chapter 02 adds openpyxl 3.1.x for Excel workbook creation/loading, formulas and cached values, styles, worksheet tables, data-validation metadata, optimized read/write modes, macro-preservation boundaries, safe round trips, and deterministic workbook verification. Executable external-library examples use the dependency contract declared in [`requirements-external.txt`](../requirements-external.txt).
## Phase 10: Practical projects
diff --git a/docs/roadmap.es.md b/docs/roadmap.es.md
index a21dd25..17dc2ed 100644
--- a/docs/roadmap.es.md
+++ b/docs/roadmap.es.md
@@ -23,10 +23,10 @@ Este roadmap acompaña tanto la ruta educativa como la base del repositorio que
| 6. Comentarios, documentación y código limpio | Completada | Seis capítulos revisados están disponibles y la sección educativa piloto está oficialmente completada |
| 7. Errores, archivos y módulos | Completada | Cinco capítulos revisados cubren manejo de excepciones, señalización deliberada, I/O seguro de archivos, formatos TXT/CSV/JSON e imports/módulos/paquetes |
| 8. Biblioteca estándar | Completada | Nueve capítulos revisados cubren rutas, fecha/hora, JSON, CSV, logging, colecciones especializadas, iteración lazy, aritmética decimal y operaciones de OS/filesystem |
-| 9. Bibliotecas externas | En progreso | Capítulo de pandas 3.0.x disponible con contrato explícito de dependencias de terceros y ejemplos deterministas |
+| 9. Bibliotecas externas | En progreso | Capítulos de pandas 3.0.x y openpyxl 3.1.x disponibles con contratos explícitos de dependencias y ejemplos deterministas |
| 10. Proyectos prácticos | Planificada | Contenido todavía no iniciado |
-Las Fases 0–8 están completadas. La Fase 9: Bibliotecas Externas está ahora en progreso con `pandas` como primer capítulo. La fase introduce contratos explícitos de dependencias de terceros mediante `requirements-external.txt`, mientras `openpyxl`, `requests` y `pytest` siguen planificados.
+Las Fases 0–8 están completadas. La Fase 9: Bibliotecas Externas está en progreso con `pandas` y `openpyxl` disponibles. La fase ahora cubre transformación de datos tabulares y automatización de libros de Excel mediante contratos explícitos de dependencias de terceros, mientras `requests` y `pytest` siguen planificados.
## Fase 0: Base del proyecto
@@ -157,11 +157,11 @@ La Fase 8 está completada. Los Capítulos 01–08 construyen contratos para rut
Consulta la [ruta de aprendizaje de la sección](../external-libraries/README.es.md).
- [x] [`pandas`](../external-libraries/01-pandas/README.es.md)
-- [ ] `openpyxl`
+- [x] [`openpyxl`](../external-libraries/02-openpyxl/README.es.md)
- [ ] `requests`
- [ ] `pytest`
-La Fase 9 está en progreso. El Capítulo 01 introduce pandas 3.0.x para datos tabulares etiquetados, selección, Copy-on-Write, política de datos ausentes, transformaciones vectorizadas, agrupaciones, joins validados, reshape y pipelines CSV. Los ejemplos ejecutables de bibliotecas externas usan el contrato de dependencias declarado en [`requirements-external.txt`](../requirements-external.txt).
+La Fase 9 está en progreso. El Capítulo 01 introduce pandas 3.0.x para datos tabulares etiquetados, selección, Copy-on-Write, política de datos ausentes, transformaciones vectorizadas, agrupaciones, joins validados, reshape y pipelines CSV. El Capítulo 02 añade openpyxl 3.1.x para creación/carga de libros de Excel, fórmulas y valores en caché, estilos, tablas, metadatos de validación, modos optimizados de lectura/escritura, límites de preservación de macros, round-trips seguros y verificación determinista de libros. Los ejemplos ejecutables de bibliotecas externas usan el contrato declarado en [`requirements-external.txt`](../requirements-external.txt).
## Fase 10: Proyectos prácticos
diff --git a/docs/roadmap.pt-BR.md b/docs/roadmap.pt-BR.md
index f35c799..77c9783 100644
--- a/docs/roadmap.pt-BR.md
+++ b/docs/roadmap.pt-BR.md
@@ -23,10 +23,10 @@ Este roadmap acompanha tanto a trilha educacional quanto a fundação do reposit
| 6. Comentários, documentação e código limpo | Concluída | Seis capítulos revisados estão disponíveis e a seção educacional-piloto está oficialmente concluída |
| 7. Erros, arquivos e módulos | Concluída | Cinco capítulos revisados cobrem tratamento de exceções, sinalização deliberada, I/O seguro de arquivos, formatos TXT/CSV/JSON e imports/módulos/pacotes |
| 8. Biblioteca padrão | Concluída | Nove capítulos revisados cobrem caminhos, data/hora, JSON, CSV, logging, coleções especializadas, iteração lazy, aritmética decimal e operações de OS/filesystem |
-| 9. Bibliotecas externas | Em andamento | Capítulo de pandas 3.0.x disponível com contrato explícito de dependências de terceiros e exemplos determinísticos |
+| 9. Bibliotecas externas | Em andamento | Capítulos de pandas 3.0.x e openpyxl 3.1.x disponíveis com contratos explícitos de dependências e exemplos determinísticos |
| 10. Projetos práticos | Planejada | Conteúdo ainda não iniciado |
-As Fases 0–8 estão concluídas. A Fase 9: Bibliotecas Externas agora está em andamento com `pandas` como primeiro capítulo. A fase introduz contratos explícitos de dependências de terceiros por meio de `requirements-external.txt`, enquanto `openpyxl`, `requests` e `pytest` permanecem planejados.
+As Fases 0–8 estão concluídas. A Fase 9: Bibliotecas Externas está em andamento com `pandas` e `openpyxl` disponíveis. A fase agora cobre transformação de dados tabulares e automação de workbooks do Excel por meio de contratos explícitos de dependências de terceiros, enquanto `requests` e `pytest` permanecem planejados.
## Fase 0: Fundação do projeto
@@ -157,11 +157,11 @@ A Fase 8 está concluída. Os Capítulos 01–08 constroem contratos para caminh
Veja a [trilha de aprendizagem da seção](../external-libraries/README.pt-BR.md).
- [x] [`pandas`](../external-libraries/01-pandas/README.pt-BR.md)
-- [ ] `openpyxl`
+- [x] [`openpyxl`](../external-libraries/02-openpyxl/README.pt-BR.md)
- [ ] `requests`
- [ ] `pytest`
-A Fase 9 está em andamento. O Capítulo 01 introduz pandas 3.0.x para dados tabulares rotulados, seleção, Copy-on-Write, política de dados ausentes, transformações vetorizadas, agrupamentos, joins validados, reshape e pipelines CSV. Os exemplos executáveis de bibliotecas externas usam o contrato de dependências declarado em [`requirements-external.txt`](../requirements-external.txt).
+A Fase 9 está em andamento. O Capítulo 01 introduz pandas 3.0.x para dados tabulares rotulados, seleção, Copy-on-Write, política de dados ausentes, transformações vetorizadas, agrupamentos, joins validados, reshape e pipelines CSV. O Capítulo 02 acrescenta openpyxl 3.1.x para criação/carregamento de workbooks do Excel, fórmulas e valores em cache, estilos, tabelas, metadados de validação, modos otimizados de leitura/escrita, limites de preservação de macros, round-trips seguros e verificação determinística de workbooks. Os exemplos executáveis de bibliotecas externas usam o contrato declarado em [`requirements-external.txt`](../requirements-external.txt).
## Fase 10: Projetos práticos
diff --git a/external-libraries/01-pandas/README.es.md b/external-libraries/01-pandas/README.es.md
index e5752cb..80b99a8 100644
--- a/external-libraries/01-pandas/README.es.md
+++ b/external-libraries/01-pandas/README.es.md
@@ -1365,10 +1365,8 @@ Referencias principales usadas para este capítulo:
La documentación oficial identifica pandas 3.0.5 como la documentación estable usada en este capítulo, y pandas 3.0 requiere Python 3.11 o superior.
-## 65. Siguiente capítulo
+## 65. Próximo capítulo
-Este capítulo abre la **Fase 9: Bibliotecas Externas**.
+Después de `pandas`, la ruta continúa con **`openpyxl`**, pasando de la semántica de tablas a la estructura de libros de Excel.
-La siguiente biblioteca planificada es **`openpyxl`**, enfocada en operaciones programáticas sobre libros de Excel.
-
-Antes de avanzar, practica pandas con datasets suficientemente pequeños para poder inspeccionarlos manualmente. Una biblioteca tabular solo se vuelve útil cuando todavía puedes razonar sobre lo que debería hacer cada transformación.
+Continúa con [`openpyxl`: Automatizando Libros de Excel](../02-openpyxl/README.es.md) para estudiar operaciones con hojas/celdas, límites de fórmulas, estilos, tablas, metadatos de validación, modos optimizados y round-trips seguros.
diff --git a/external-libraries/01-pandas/README.md b/external-libraries/01-pandas/README.md
index b03f008..b2ce015 100644
--- a/external-libraries/01-pandas/README.md
+++ b/external-libraries/01-pandas/README.md
@@ -1367,8 +1367,6 @@ The official documentation identifies pandas 3.0.5 as the current stable documen
## 65. Next chapter
-This chapter opens **Phase 9: External Libraries**.
+`pandas` is now followed by **`openpyxl`**, which moves from table semantics to Excel workbook structure.
-The next planned library is **`openpyxl`**, focused on programmatic Excel workbook operations.
-
-Before moving on, practice pandas with datasets small enough to inspect manually. A table library becomes useful only when you can still reason about what each transformation should do.
+Continue with [`openpyxl`: Automating Excel Workbooks](../02-openpyxl/README.md) to learn worksheet/cell operations, formula boundaries, styles, tables, validation metadata, optimized modes, and safe workbook round trips.
diff --git a/external-libraries/01-pandas/README.pt-BR.md b/external-libraries/01-pandas/README.pt-BR.md
index 7e88d73..7835e0f 100644
--- a/external-libraries/01-pandas/README.pt-BR.md
+++ b/external-libraries/01-pandas/README.pt-BR.md
@@ -1367,8 +1367,6 @@ A documentação oficial identifica pandas 3.0.5 como a documentação estável
## 65. Próximo capítulo
-Este capítulo abre a **Fase 9: Bibliotecas Externas**.
+Depois de `pandas`, a trilha segue para **`openpyxl`**, passando da semântica de tabelas para a estrutura de workbooks do Excel.
-A próxima biblioteca planejada é **`openpyxl`**, com foco em operações programáticas sobre workbooks do Excel.
-
-Antes de avançar, pratique pandas com conjuntos de dados pequenos o suficiente para serem inspecionados manualmente. Uma biblioteca de tabelas só se torna útil quando você ainda consegue raciocinar sobre o que cada transformação deveria fazer.
+Continue com [`openpyxl`: Automatizando Workbooks do Excel](../02-openpyxl/README.pt-BR.md) para estudar operações com planilhas/células, limites de fórmulas, estilos, tabelas, metadados de validação, modos otimizados e round-trips seguros.
diff --git a/external-libraries/02-openpyxl/README.es.md b/external-libraries/02-openpyxl/README.es.md
new file mode 100644
index 0000000..aec198c
--- /dev/null
+++ b/external-libraries/02-openpyxl/README.es.md
@@ -0,0 +1,1139 @@
+
+
+# Automatizando Libros de Excel con `openpyxl`
+
+[🇺🇸 English](README.md) · [🇧🇷 Português](README.pt-BR.md) · [🇪🇸 Español](README.es.md)
+
+
+
+[← Volver a Bibliotecas Externas](../README.es.md) · [← Anterior: `pandas`](../01-pandas/README.es.md)
+
+`pandas` trata los datos similares a hojas de cálculo principalmente como tablas. `openpyxl` trabaja en otra capa: el propio libro de Excel. Permite que Python cree, inspeccione, edite, formatee y guarde libros Office Open XML conservando conceptos como hojas, celdas, fórmulas, estilos, tablas, validaciones, gráficos y configuración de impresión.
+
+Este capítulo apunta a **openpyxl 3.1.x** y fue investigado con la documentación actual de la serie 3.1 y el paquete estable **openpyxl 3.1.5** publicado en PyPI. PyPI declara Python 3.8 o superior; este repositorio valida los ejemplos en Python 3.13.
+
+**Tiempo estimado de estudio:** 240–330 minutos.
+
+## Objetivos de aprendizaje
+
+Al finalizar este capítulo, deberías poder:
+
+- explicar cuándo `openpyxl` encaja mejor que `pandas` o el módulo estándar `csv`;
+- crear, cargar, inspeccionar y guardar libros `.xlsx`;
+- trabajar de forma segura con hojas, celdas, rangos e iteración por filas;
+- distinguir fórmulas de valores calculados;
+- entender qué significan realmente `data_only`, `read_only`, `write_only` y `keep_vba`;
+- aplicar estilos reutilizables, formatos numéricos, dimensiones y paneles congelados;
+- crear tablas, reglas de validación, filtros, comentarios, hipervínculos y gráficos;
+- comprender los límites de celdas combinadas, movimiento de filas/columnas, traducción de fórmulas, preservación de VBA y fidelidad de round-trip;
+- elegir modos optimizados para libros grandes;
+- tratar archivos de hoja de cálculo como entrada externa con límites explícitos de seguridad y validación;
+- combinar `pandas` y `openpyxl` sin confundir sus responsabilidades;
+- construir automatizaciones deterministas de libros que puedan revisarse y probarse sin Microsoft Excel instalado.
+
+## 1. Por qué existe `openpyxl`
+
+Los libros de Excel contienen más que datos rectangulares. Pueden contener varias hojas, fórmulas, formato, tablas, reglas de validación, regiones combinadas, gráficos, comentarios, hipervínculos, configuración de impresión y metadatos del libro.
+
+`openpyxl` es una biblioteca Python de terceros para leer y escribir archivos de hoja de cálculo Office Open XML, como `.xlsx` y `.xlsm`.
+
+Úsala cuando la **estructura del libro en sí importa**.
+
+## 2. `pandas` y `openpyxl` resuelven problemas diferentes
+
+Una distinción útil es:
+
+```text
+pandas -> manipulate tabular data
+openpyxl -> manipulate Excel workbook structure
+```
+
+Si necesitas agrupar diez millones de filas, `pandas` suele ser una abstracción más fuerte. Si necesitas establecer `B2` como fórmula, congelar la primera fila, aplicar un formato numérico, crear una tabla de Excel o conservar el diseño del libro, `openpyxl` es la capa más natural.
+
+Muchos flujos reales usan ambos.
+
+## 3. Las bibliotecas externas requieren un contrato de dependencias
+
+El repositorio declara las dependencias ejecutables de la Fase 9 en `requirements-external.txt`.
+
+Para este capítulo, el contrato es:
+
+```text
+openpyxl >= 3.1 and < 3.2
+```
+
+Fijar una serie minor compatible evita enseñar silenciosamente contra una API futura desconocida y permite releases de patch compatibles.
+
+## 4. Instala la dependencia en un entorno aislado
+
+Crea un entorno virtual:
+
+```bash
+python -m venv .venv
+```
+
+Actívalo según tu sistema operativo e instala el contrato del repositorio:
+
+```bash
+python -m pip install -r requirements-external.txt
+```
+
+Un `pip install openpyxl` directo es válido para experimentar, pero un archivo de dependencias hace reproducible el entorno del proyecto.
+
+## 5. Conoce los formatos de libro dentro del alcance
+
+`openpyxl` está diseñado alrededor de formatos Office Open XML como:
+
+```text
+.xlsx
+.xlsm
+.xltx
+.xltm
+```
+
+No es un lector general para todos los archivos que Excel puede abrir. En particular, los archivos binarios heredados `.xls` y los libros `.xlsb` son formatos diferentes y requieren otras herramientas.
+
+Trata la extensión como parte del contrato de entrada.
+
+## 6. Crea un libro
+
+La clase central es `Workbook`:
+
+```python
+from openpyxl import Workbook
+
+
+workbook = Workbook()
+worksheet = workbook.active
+print(worksheet.title)
+```
+
+```text
+Sheet
+```
+
+Un libro normal nuevo comienza con una hoja activa.
+
+## 7. Da nombres significativos a las hojas
+
+Renombra la hoja activa o crea hojas adicionales explícitamente:
+
+```python
+from openpyxl import Workbook
+
+
+workbook = Workbook()
+summary = workbook.active
+summary.title = "Summary"
+details = workbook.create_sheet("Details")
+print(workbook.sheetnames)
+```
+
+```text
+['Summary', 'Details']
+```
+
+Los nombres de hojas forman parte de la navegación del libro y también pueden aparecer en fórmulas y nombres definidos.
+
+## 8. Selecciona una hoja por nombre
+
+Usa acceso similar a un mapeo:
+
+```python
+from openpyxl import Workbook
+
+
+workbook = Workbook()
+workbook.active.title = "Summary"
+worksheet = workbook["Summary"]
+print(worksheet.title)
+```
+
+Evita depender de la posición física de una hoja cuando su nombre es el contrato real.
+
+## 9. Las celdas usan coordenadas estilo Excel
+
+Las celdas pueden accederse con coordenadas como `A1`:
+
+```python
+from openpyxl import Workbook
+
+
+workbook = Workbook()
+worksheet = workbook.active
+worksheet["A1"] = "status"
+worksheet["B1"] = "ready"
+print(worksheet["B1"].value)
+```
+
+```text
+ready
+```
+
+Las coordenadas son convenientes cuando el diseño del libro es fijo y significativo.
+
+## 10. `cell()` usa índices de fila y columna desde uno
+
+La generación programática suele encajar mejor con `Worksheet.cell()`:
+
+```python
+from openpyxl import Workbook
+
+
+workbook = Workbook()
+worksheet = workbook.active
+worksheet.cell(row=2, column=3, value=42)
+print(worksheet["C2"].value)
+```
+
+```text
+42
+```
+
+Las filas y columnas de Excel se indexan desde uno en esta API.
+
+## 11. Acceder a celdas puede crearlas en memoria
+
+Una hoja normal crea objetos de celda cuando se acceden por primera vez. Por eso, un bucle sobre un rango gigantesco e innecesario puede asignar muchas celdas incluso sin guardar datos útiles.
+
+No recorras un rectángulo de un millón por un millón solo para descubrir qué celdas existen.
+
+Usa rangos conocidos, dimensiones de hoja o lectura optimizada cuando corresponda.
+
+## 12. Agrega filas completas con `append()`
+
+Para salida orientada a filas, `append()` suele ser más claro que asignar cada coordenada:
+
+```python
+from openpyxl import Workbook
+
+
+workbook = Workbook()
+worksheet = workbook.active
+worksheet.append(["item", "quantity"])
+worksheet.append(["Cable", 3])
+worksheet.append(["Adapter", 2])
+print(worksheet.max_row)
+```
+
+```text
+3
+```
+
+Funciona bien para exportaciones construidas registro por registro.
+
+## 13. Itera filas en lugar de codificar cada celda
+
+`iter_rows()` expone una región rectangular:
+
+```python
+from openpyxl import Workbook
+
+
+workbook = Workbook()
+worksheet = workbook.active
+worksheet.append(["name", "score"])
+worksheet.append(["A", 8])
+worksheet.append(["B", 9])
+
+for row in worksheet.iter_rows(min_row=2, values_only=True):
+ print(row)
+```
+
+```text
+('A', 8)
+('B', 9)
+```
+
+`values_only=True` devuelve valores Python en lugar de objetos `Cell` cuando no necesitas metadatos de celda.
+
+## 14. Itera columnas solo cuando el patrón de acceso lo necesite
+
+Las hojas normales también admiten `iter_cols()`. La iteración por filas suele ser más natural para datos tipo registro, mientras la iteración por columnas sirve cuando la regla del libro está orientada a una columna.
+
+El modo read-only optimizado tiene una API más limitada, así que no diseñes todo el flujo alrededor de métodos que allí no existen.
+
+## 15. Las dimensiones de hoja son una pista, no una regla de negocio
+
+Propiedades como `max_row`, `max_column` y `calculate_dimension()` describen la región aparentemente usada.
+
+No prueban que cada celda dentro de esa región contenga datos significativos.
+
+Celdas vacías pero formateadas, metadatos antiguos o generadores de terceros pueden hacer que las dimensiones sean mayores o menores de lo esperado.
+
+## 16. Guarda deliberadamente en una ruta nueva
+
+Un libro se persiste con `save()`:
+
+```python
+from pathlib import Path
+from tempfile import TemporaryDirectory
+
+from openpyxl import Workbook
+
+
+with TemporaryDirectory() as temp_dir:
+ path = Path(temp_dir) / "report.xlsx"
+ workbook = Workbook()
+ workbook.active["A1"] = "ready"
+ workbook.save(path)
+ print(path.exists())
+```
+
+```text
+True
+```
+
+En automatizaciones de producción, prefiere una ruta de salida deliberada antes que sobrescribir casualmente el libro fuente.
+
+## 17. Carga un libro existente
+
+Usa `load_workbook()`:
+
+```python
+from pathlib import Path
+from tempfile import TemporaryDirectory
+
+from openpyxl import Workbook, load_workbook
+
+
+with TemporaryDirectory() as temp_dir:
+ path = Path(temp_dir) / "input.xlsx"
+ workbook = Workbook()
+ workbook.active["A1"] = "loaded"
+ workbook.save(path)
+
+ reloaded = load_workbook(path)
+ print(reloaded.active["A1"].value)
+ reloaded.close()
+```
+
+```text
+loaded
+```
+
+Cerrar explícitamente es especialmente importante para libros read-only y es un buen hábito para recursos respaldados por archivos.
+
+## 18. Un round-trip puede perder características no soportadas
+
+Abrir un libro complejo y guardarlo otra vez no garantiza conservar cada artefacto creado por Excel u otra aplicación.
+
+El tutorial oficial advierte que openpyxl no lee todos los elementos posibles de un libro y que algunas shapes pueden perderse durante un round-trip load/save.
+
+Por lo tanto:
+
+```text
+load -> edit one cell -> save
+```
+
+no es automáticamente una transformación sin pérdidas para cualquier libro.
+
+## 19. `read_only=True` es un modo de operación diferente
+
+Los libros grandes pueden consumir mucha memoria. El modo read-only transmite el contenido de la hoja de forma lazy:
+
+```python
+from openpyxl import load_workbook
+
+
+workbook = load_workbook("large.xlsx", read_only=True, data_only=True)
+worksheet = workbook["Data"]
+for row in worksheet.iter_rows(values_only=True):
+ process = row
+workbook.close()
+```
+
+El ejemplo es intencionalmente ilustrativo y no ejecutable en el repositorio porque depende de un archivo externo.
+
+Las hojas read-only no son hojas editables normales.
+
+## 20. El modo read-only debe cerrarse explícitamente
+
+La documentación oficial de modos optimizados destaca `close()` para libros read-only.
+
+Usa un límite `try/finally` cuando el procesamiento posterior pueda fallar:
+
+```python
+from openpyxl import load_workbook
+
+
+workbook = load_workbook("large.xlsx", read_only=True)
+try:
+ worksheet = workbook.active
+ for row in worksheet.iter_rows(values_only=True):
+ process = row
+finally:
+ workbook.close()
+```
+
+La liberación de recursos debe sobrevivir a excepciones.
+
+## 21. Las dimensiones en read-only pueden ser incorrectas
+
+La lectura lazy depende de metadatos de dimensión almacenados en el libro. Algunas aplicaciones productoras escriben esos datos de forma incorrecta.
+
+La documentación recomienda revisar `calculate_dimension()` y, cuando sabes que el metadato está mal, usar `reset_dimensions()` sobre una hoja read-only.
+
+Hazlo solo cuando exista una razón externa para saber que las dimensiones guardadas son incorrectas.
+
+## 22. `write_only=True` está optimizado para salida en streaming
+
+Los libros write-only se crean de forma diferente:
+
+```python
+from openpyxl import Workbook
+
+
+workbook = Workbook(write_only=True)
+worksheet = workbook.create_sheet("Data")
+worksheet.append(["id", "value"])
+worksheet.append([1, 10])
+worksheet.append([2, 20])
+```
+
+A diferencia de un `Workbook()` normal, un libro write-only empieza sin hojas. Debes crear una explícitamente.
+
+## 23. El modo write-only está orientado a `append()`
+
+Una hoja write-only está diseñada para salida secuencial. Las filas se agregan con `append()` en vez de lectura y escritura arbitraria de celdas.
+
+Es una buena opción para grandes exportaciones donde los registros llegan en orden y no necesitas volver a editar filas antiguas.
+
+## 24. Un libro write-only solo puede guardarse una vez
+
+La documentación de modos optimizados establece que un libro write-only puede guardarse una sola vez.
+
+El flujo debe ser:
+
+```text
+configure workbook -> append rows -> save once
+```
+
+y no:
+
+```text
+save -> append more -> save again
+```
+
+Configura todo lo que deba aparecer antes de los datos antes de empezar el streaming de filas.
+
+## 25. Elige conscientemente entre normal, read-only y write-only
+
+| Necesidad | Prefiere |
+|---|---|
+| editar celdas arbitrarias | libro normal |
+| inspeccionar estilos, gráficos, imágenes y estructura completa | libro normal |
+| transmitir una hoja existente muy grande | `read_only=True` |
+| transmitir una exportación nueva muy grande | `Workbook(write_only=True)` |
+| guardar repetidamente mientras editas | libro normal |
+
+Los modos optimizados intercambian capacidades por menor uso de memoria.
+
+## 26. Los valores Python se convierten en valores de celda
+
+Las celdas pueden almacenar valores Python comunes como strings, números, booleanos, fechas, datetimes y fórmulas representadas como strings que empiezan con `=`.
+
+Mantén separada la validación de dominio. Que un valor pueda almacenarse en una celda no significa que sea válido para tu aplicación.
+
+## 27. Las fechas son valores más formatos numéricos
+
+Excel almacena valores de fecha/hora con semántica de fecha de hoja de cálculo y los muestra mediante formatos numéricos.
+
+Al asignar un `datetime` de Python, openpyxl aplica automáticamente un formato compatible:
+
+```python
+from datetime import datetime
+
+from openpyxl import Workbook
+
+
+workbook = Workbook()
+worksheet = workbook.active
+worksheet["A1"] = datetime(2026, 8, 29, 14, 30)
+print(worksheet["A1"].is_date)
+```
+
+```text
+True
+```
+
+No trates el texto visible en Excel como la única representación relevante.
+
+## 28. Excel tiene dos sistemas de fechas
+
+Las fechas de hoja de cálculo pueden usar el sistema 1900 o 1904 según la configuración e historia del libro.
+
+Deja que el libro y openpyxl administren la conversión en lugar de sumar manualmente un número fijo de días a valores seriales.
+
+La aritmética manual de seriales facilita errores de época y desplazamiento.
+
+## 29. Las fórmulas se almacenan como fórmulas
+
+Asigna un string de fórmula que empiece con `=`:
+
+```python
+from openpyxl import Workbook
+
+
+workbook = Workbook()
+worksheet = workbook.active
+worksheet["A1"] = 10
+worksheet["A2"] = 20
+worksheet["A3"] = "=SUM(A1:A2)"
+print(worksheet["A3"].value)
+```
+
+```text
+=SUM(A1:A2)
+```
+
+La celda contiene una expresión de fórmula, no un cálculo Python.
+
+## 30. `openpyxl` no calcula fórmulas
+
+Este es uno de los límites más importantes de la biblioteca.
+
+`openpyxl` puede leer y escribir expresiones de fórmula, pero no es un motor de cálculo de Excel. Escribir `=SUM(A1:A2)` no hace que openpyxl calcule `30`.
+
+Si tu flujo Python necesita el resultado en ese momento, calcula el valor en Python o usa un motor de cálculo separado con contrato documentado.
+
+## 31. `data_only=True` lee resultados en caché
+
+Al cargar un libro, `data_only` controla si las celdas con fórmula exponen la fórmula o el valor en caché dejado por la última aplicación de hoja de cálculo que calculó el libro.
+
+```text
+load_workbook(path, data_only=False) -> formula text
+load_workbook(path, data_only=True) -> cached result, if available
+```
+
+Un libro recién creado puede no tener ningún valor calculado en caché.
+
+No confundas `data_only=True` con “calcular fórmulas ahora”.
+
+## 32. Los nombres de funciones se escriben en inglés
+
+La documentación de fórmulas de openpyxl indica que los nombres de funciones deben estar en inglés y los argumentos se separan con comas.
+
+Por ejemplo:
+
+```python
+from openpyxl import Workbook
+
+
+workbook = Workbook()
+worksheet = workbook.active
+worksheet["A1"] = "=SUM(1,2,3)"
+print(worksheet["A1"].value)
+```
+
+```text
+=SUM(1,2,3)
+```
+
+No generes sintaxis localizada basándote en cómo Excel muestra fórmulas en una máquina concreta.
+
+## 33. Los estilos son objetos del libro, no strings de apariencia
+
+Componentes comunes incluyen:
+
+```text
+Font
+PatternFill / GradientFill
+Border
+Alignment
+Protection
+number_format
+```
+
+El modelo es explícito porque la apariencia de una celda de Excel se compone de varias propiedades independientes.
+
+## 34. Aplica fuente, relleno y alineación
+
+```python
+from openpyxl import Workbook
+from openpyxl.styles import Alignment, Font, PatternFill
+
+
+workbook = Workbook()
+worksheet = workbook.active
+cell = worksheet["A1"]
+cell.value = "Header"
+cell.font = Font(bold=True)
+cell.fill = PatternFill(fill_type="solid", fgColor="D9EAF7")
+cell.alignment = Alignment(horizontal="center")
+print(cell.font.bold)
+```
+
+```text
+True
+```
+
+El formato debe comunicar estructura, no compensar datos poco claros.
+
+## 35. Los estilos de celda son efectivamente inmutables después de asignarse
+
+La documentación oficial explica que los componentes de estilo asignados se comparten y no pueden mutarse in-place.
+
+Esto es deliberadamente inválido:
+
+```text
+a1.font.italic = True
+```
+
+Asigna un nuevo objeto `Font`:
+
+```python
+from openpyxl import Workbook
+from openpyxl.styles import Font
+
+
+workbook = Workbook()
+worksheet = workbook.active
+worksheet["A1"].font = Font(color="FF0000")
+worksheet["A1"].font = Font(color="FF0000", italic=True)
+print(worksheet["A1"].font.italic)
+```
+
+```text
+True
+```
+
+## 36. Reutiliza estilos en lugar de crear miles de variaciones
+
+Si muchas celdas comparten el mismo rol visual, reutiliza la misma definición o un `NamedStyle`.
+
+Crear objetos ligeramente distintos para cada celda puede inflar la tabla de estilos y el tamaño del archivo.
+
+Trata los estilos como vocabulario controlado: encabezado, moneda, fecha, advertencia, entrada, salida.
+
+## 37. Los formatos numéricos cambian la visualización, no el valor almacenado
+
+```python
+from openpyxl import Workbook
+
+
+workbook = Workbook()
+worksheet = workbook.active
+worksheet["A1"] = 0.125
+worksheet["A1"].number_format = "0.00%"
+print(worksheet["A1"].value)
+```
+
+```text
+0.125
+```
+
+Excel puede mostrar `12.50%`, pero el valor almacenado sigue siendo `0.125`.
+
+La diferencia importa cuando otro programa lee el libro.
+
+## 38. Los named styles hacen explícito el formato repetido
+
+```python
+from openpyxl import Workbook
+from openpyxl.styles import Font, NamedStyle
+
+
+workbook = Workbook()
+worksheet = workbook.active
+header = NamedStyle(name="header")
+header.font = Font(bold=True)
+workbook.add_named_style(header)
+worksheet["A1"].style = "header"
+print(worksheet["A1"].style)
+```
+
+```text
+header
+```
+
+Una vez asignado un named style a una celda, cambios posteriores al `NamedStyle` no vuelven a estilizar esa celda de forma retroactiva.
+
+## 39. El ancho de columna y la altura de fila son metadatos de diseño
+
+```python
+from openpyxl import Workbook
+
+
+workbook = Workbook()
+worksheet = workbook.active
+worksheet.column_dimensions["A"].width = 24
+worksheet.row_dimensions[1].height = 30
+print(worksheet.column_dimensions["A"].width)
+```
+
+```text
+24.0
+```
+
+No supongas que openpyxl reproducirá el AutoFit interactivo de Excel solo a partir del contenido.
+
+## 40. Freeze panes conserva contexto al desplazarse
+
+```python
+from openpyxl import Workbook
+
+
+workbook = Workbook()
+worksheet = workbook.active
+worksheet.freeze_panes = "A2"
+print(worksheet.freeze_panes)
+```
+
+```text
+A2
+```
+
+`A2` congela las filas por encima de la fila 2, manteniendo visible la primera fila.
+
+## 41. Las celdas combinadas tienen una sola celda real de valor
+
+Cuando se combina un rango, solo la celda superior izquierda es la celda normal que contiene valor. Las demás posiciones se convierten en placeholders de merged cell.
+
+```python
+from openpyxl import Workbook
+
+
+workbook = Workbook()
+worksheet = workbook.active
+worksheet.merge_cells("A1:C1")
+worksheet["A1"] = "Quarterly report"
+print(worksheet["A1"].value)
+```
+
+```text
+Quarterly report
+```
+
+Las celdas combinadas son estructura de presentación, no sustituto de datos tabulares normalizados.
+
+## 42. Insertar y eliminar no administra todas las dependencias
+
+`insert_rows()`, `delete_rows()`, `insert_cols()` y `delete_cols()` pueden desplazar celdas.
+
+La documentación oficial indica que openpyxl no administra todas las dependencias que puedan referenciar esas celdas, como fórmulas, tablas o gráficos.
+
+Una edición estructural puede requerir lógica específica de la aplicación para reparar referencias.
+
+## 43. `move_range()` puede traducir algunas fórmulas, no todas las referencias
+
+`move_range(..., translate=True)` puede traducir fórmulas dentro de las celdas movidas.
+
+Sin embargo, referencias a esas celdas desde otras celdas o nombres definidos no se actualizan automáticamente.
+
+No confundas “celdas movidas” con “semántica del libro reparada”.
+
+## 44. Las tablas de hoja agregan semántica de tabla de Excel
+
+Una tabla de worksheet es más que un rango coloreado. Tiene nombre y una referencia de celdas definida:
+
+```python
+from openpyxl import Workbook
+from openpyxl.worksheet.table import Table
+
+
+workbook = Workbook()
+worksheet = workbook.active
+worksheet.append(["item", "amount"])
+worksheet.append(["A", 10])
+worksheet.append(["B", 20])
+table = Table(displayName="SalesTable", ref="A1:B3")
+worksheet.add_table(table)
+print(list(worksheet.tables.keys()))
+```
+
+```text
+['SalesTable']
+```
+
+Las tablas son útiles cuando usuarios posteriores de Excel esperan referencias estructuradas y formato consciente de tabla.
+
+## 45. Los nombres y encabezados de tabla son contratos
+
+Los display names deben ser válidos y únicos dentro del namespace correspondiente. La documentación también requiere que los encabezados de las columnas sean strings.
+
+Valida los encabezados antes de crear la tabla en lugar de depender de que Excel repare una salida defectuosa.
+
+## 46. Los filtros describen comportamiento del libro; no filtran datos Python
+
+Los auto filters pueden configurarse para que una aplicación de hoja de cálculo sepa qué filas mostrar según determinados criterios.
+
+Eso es distinto de filtrar registros en Python antes de escribirlos.
+
+Si un reporte debe contener físicamente solo filas aprobadas, filtra primero los datos Python. Si los usuarios necesitan filtrado interactivo en Excel, configura una tabla o auto filter como comportamiento de presentación.
+
+## 47. Las reglas de validación se escriben, no se ejecutan por openpyxl
+
+La documentación oficial es explícita: los validadores no son aplicados ni evaluados por openpyxl.
+
+```python
+from openpyxl import Workbook
+from openpyxl.worksheet.datavalidation import DataValidation
+
+
+workbook = Workbook()
+worksheet = workbook.active
+validation = DataValidation(type="list", formula1='"open,closed"')
+worksheet.add_data_validation(validation)
+validation.add("A2:A20")
+print(len(worksheet.data_validations.dataValidation))
+```
+
+```text
+1
+```
+
+La regla pasa a ser metadato que Excel u otra aplicación compatible puede aplicar de forma interactiva.
+
+## 48. El formato condicional también es comportamiento del libro
+
+Las reglas de conditional formatting indican a una aplicación cómo formatear celdas cuando se cumplen condiciones.
+
+No uses formato condicional como sustituto oculto de validación. Una celda roja puede comunicar un error a una persona, pero tu programa Python debe validar entradas críticas explícitamente.
+
+## 49. Los gráficos referencian datos de hoja
+
+`openpyxl.chart` puede construir gráficos a partir de rangos. Un flujo típico crea un chart, define objetos `Reference` para datos y categorías y ancla el gráfico en una hoja.
+
+Los gráficos son objetos de presentación sobre datos. Prueba los números subyacentes por separado del diseño visual.
+
+## 50. Las imágenes introducen una dependencia opcional de Pillow
+
+La API de imágenes puede insertar imágenes raster en hojas, pero su manejo depende de Pillow.
+
+Como el contrato ejecutable de este capítulo no necesita imágenes, Pillow no se agrega solo por un ejemplo decorativo.
+
+Agrega dependencias opcionales únicamente cuando el proyecto realmente necesite la función.
+
+## 51. Comentarios e hipervínculos son metadatos de celda
+
+Las celdas pueden tener comentarios e hipervínculos además de valores y estilos.
+
+Úsalos cuando aporten contexto útil a personas, pero conserva la información esencial legible por máquina en celdas normales o datos estructurados, no escondida en comentarios.
+
+## 52. Los nombres definidos pueden representar referencias de libro
+
+Los defined names de Excel pueden apuntar a celdas, rangos, constantes o fórmulas y pueden tener alcance de libro o de hoja.
+
+Son útiles para contratos de workbook, pero crean otra capa de dependencia cuando se mueven celdas o se renombran hojas.
+
+Inspecciona los nombres definidos antes de realizar ediciones estructurales en plantillas complejas.
+
+## 53. La protección de hoja no es cifrado
+
+La protección de celdas y hojas controla comportamiento de edición en la interfaz. No sustituye cifrado de archivos sensibles ni autorización del lado servidor.
+
+Trata la protección del libro como una restricción de interfaz, no como una frontera de seguridad.
+
+## 54. La configuración de impresión forma parte del producto
+
+Orientación de página, márgenes, áreas de impresión, títulos repetidos y escala pueden importar cuando un `.xlsx` debe convertirse en PDF o imprimirse.
+
+Para un libro de intercambio de datos puede ser irrelevante. Para un reporte humano, puede formar parte de los criterios de aceptación.
+
+## 55. Entiende las flags importantes de `load_workbook()`
+
+Flags comunes incluyen:
+
+```text
+read_only=True -> lazy, lower-memory reading
+data_only=True -> cached formula results instead of formula text
+keep_vba=True -> preserve VBA content when possible
+keep_links=True -> preserve cached external-link data
+rich_text=True -> preserve rich text formatting in cells
+```
+
+Cada flag modifica el contrato. No las actives solo porque parezcan más seguras o completas.
+
+## 56. `keep_vba=True` preserva VBA; no permite editarlo
+
+El tutorial oficial indica que los elementos VBA pueden preservarse, pero no son editables mediante openpyxl.
+
+Si un `.xlsm` con macros debe hacer round-trip conservando VBA, usa la extensión correcta y `keep_vba=True`, y prueba el artefacto real.
+
+Preservar no significa ejecutar, inspeccionar ni modificar.
+
+## 57. Incompatibilidades entre plantilla y extensión pueden romper expectativas
+
+Tipo de libro, extensión del archivo y configuración de VBA/template deben coincidir.
+
+Guardar un libro con macros bajo una extensión incorrecta o ignorar el contrato VBA puede producir un archivo rechazado por Excel o que pierde funcionalidad silenciosamente.
+
+Trata explícitamente los tipos de origen y destino.
+
+## 58. Los libros no confiables son una frontera de seguridad
+
+Un `.xlsx` es un paquete ZIP con XML y recursos relacionados. La página de openpyxl en PyPI advierte que openpyxl no protege por defecto contra ataques XML de quadratic blowup o billion laughs y recomienda `defusedxml`.
+
+Para ejemplos confiables generados por el repositorio no es necesario. Para servicios que aceptan libros arbitrarios subidos por usuarios, el modelado de amenazas y el parsing XML endurecido forman parte del diseño.
+
+## 59. Los archivos inválidos deben fallar de forma visible
+
+`load_workbook()` puede rechazar archivos OOXML malformados o no conformes.
+
+Captura excepciones solo cuando puedas agregar contexto útil y conserva la falla:
+
+```python
+from pathlib import Path
+from zipfile import BadZipFile
+
+from openpyxl import load_workbook
+from openpyxl.utils.exceptions import InvalidFileException
+
+
+def read_sheet_names(path: Path) -> list[str]:
+ try:
+ workbook = load_workbook(path, read_only=True)
+ except (BadZipFile, InvalidFileException, OSError) as exc:
+ raise RuntimeError(f"Could not open workbook: {path.name}") from exc
+
+ try:
+ return workbook.sheetnames
+ finally:
+ workbook.close()
+```
+
+No conviertas todos los errores de libro en un reporte vacío.
+
+## 60. Prefiere validar la salida a aceptar solo “save funcionó”
+
+Un `save()` exitoso demuestra que se escribieron bytes. No demuestra que el libro cumpla el contrato de negocio o presentación.
+
+Comprobaciones útiles después de guardar incluyen:
+
+```text
+file exists
+expected sheet names exist
+required cells contain expected values or formulas
+expected table names exist
+expected validations exist
+critical number formats/styles are present
+workbook reopens successfully
+```
+
+Para plantillas importantes, abre también el artefacto generado en la aplicación objetivo durante las pruebas de aceptación.
+
+## 61. Ejemplo: crea un libro y conserva una fórmula
+
+[`examples/workbook_basics.py`](examples/workbook_basics.py) crea un libro temporal, añade filas y fórmulas, guarda, vuelve a cargar con fórmulas visibles y verifica la estructura.
+
+Salida esperada:
+
+```text
+sheet: Summary
+rows: 3
+formula: =B2*C2
+```
+
+El ejemplo prueba lo que openpyxl realmente controla: texto de fórmula y estructura del libro, no su cálculo.
+
+## 62. Ejemplo: transmite filas de un libro
+
+[`examples/load_and_iterate.py`](examples/load_and_iterate.py) escribe un libro pequeño, lo reabre con `read_only=True`, itera valores y calcula un total en Python.
+
+Salida esperada:
+
+```text
+orders: 3
+total: 100.00
+```
+
+Esto separa deliberadamente lectura de libro de cálculo de negocio.
+
+## 63. Ejemplo: crea un reporte con estilo
+
+[`examples/styled_report.py`](examples/styled_report.py) aplica tratamiento reutilizable de encabezado, formato numérico, freeze pane y anchos de columna, después recarga el libro para verificar metadatos persistidos.
+
+Salida esperada:
+
+```text
+header bold: True
+number format: #,##0.00
+freeze panes: A2
+```
+
+Una prueba determinista puede inspeccionar metadatos sin abrir Excel.
+
+## 64. Ejemplo: tablas y reglas de validación
+
+[`examples/table_and_validation.py`](examples/table_and_validation.py) crea una tabla de Excel y una validación por lista, guarda, recarga y verifica que ambas estructuras existan.
+
+Salida esperada:
+
+```text
+tables: ['CatalogTable']
+validations: 1
+```
+
+Recuerda que la regla de validación se almacena, no se ejecuta, por openpyxl.
+
+## 65. Ejemplo: exportación write-only
+
+[`examples/write_only_export.py`](examples/write_only_export.py) transmite filas a un libro write-only, guarda una sola vez y luego reabre el resultado en modo read-only para verificarlo.
+
+Salida esperada:
+
+```text
+rows: 3
+sum: 60
+```
+
+Esto modela el ciclo de vida de una gran exportación secuencial sin necesitar un fixture enorme.
+
+## 66. Errores comunes, guía de decisión, ejercicio y referencias
+
+Evita estos errores:
+
+- usar `openpyxl` para análisis tabular pesado que corresponde a `pandas`;
+- esperar soporte `.xls` o `.xlsb` de una biblioteca `.xlsx`;
+- asumir que `data_only=True` recalcula fórmulas;
+- sobrescribir un libro fuente complejo antes de verificar fidelidad de round-trip;
+- usar dimensiones como prueba de datos válidos;
+- usar modo normal para cargas enormes sin considerar memoria;
+- olvidar cerrar libros read-only;
+- guardar un libro write-only más de una vez;
+- mutar estilos asignados in-place;
+- crear miles de variantes de estilo casi idénticas;
+- confundir formatos numéricos con valores almacenados;
+- asumir que inserciones de filas/columnas reparan fórmulas, tablas, gráficos y nombres definidos;
+- esperar que data validation sea ejecutada por openpyxl;
+- tratar protección de hoja como seguridad;
+- preservar VBA sin probar el artefacto `.xlsm`;
+- aceptar libros no confiables sin estrategia de seguridad XML;
+- considerar `save()` por sí solo como verificación suficiente.
+
+### Tabla de decisión
+
+| Necesidad | Prefiere |
+|---|---|
+| filtrar/agrupar/unir datos | `pandas` |
+| intercambio simple CSV | `csv` o `pandas` |
+| crear/editar estructura `.xlsx` | `openpyxl` |
+| editar celdas arbitrarias | libro normal |
+| lectura secuencial grande | `read_only=True` |
+| escritura secuencial grande | `Workbook(write_only=True)` |
+| texto de fórmula | carga normal / `data_only=False` |
+| valor de fórmula en caché | `data_only=True` |
+| preservar contenedor VBA | `keep_vba=True` + contrato `.xlsm` |
+| formato repetido | estilos reutilizados / `NamedStyle` |
+| validación interactiva Excel | `DataValidation` |
+| validación de máquina | validación Python antes de escribir |
+
+### Referencia rápida
+
+```text
+from openpyxl import Workbook, load_workbook
+
+wb = Workbook()
+ws = wb.active
+ws = wb["SheetName"]
+wb.create_sheet("Details")
+
+ws["A1"] = "value"
+ws.cell(row=1, column=1, value="value")
+ws.append([...])
+ws.iter_rows(values_only=True)
+
+wb.save(path)
+wb = load_workbook(path)
+wb = load_workbook(path, read_only=True, data_only=True)
+wb.close()
+
+ws.freeze_panes = "A2"
+ws.column_dimensions["A"].width = 20
+ws["B2"].number_format = "#,##0.00"
+
+ws.merge_cells("A1:C1")
+ws.unmerge_cells("A1:C1")
+
+ws.add_table(...)
+ws.add_data_validation(...)
+```
+
+### Checklist de diseño
+
+Antes de aceptar una automatización de libro, pregunta:
+
+- ¿Qué formatos están permitidos?
+- ¿El archivo es confiable o subido por usuarios?
+- ¿Los artefactos no soportados deben sobrevivir al round-trip?
+- ¿Se puede sobrescribir la fuente?
+- ¿Qué hojas, celdas, tablas y nombres forman el contrato?
+- ¿Las fórmulas necesitan texto o valor calculado?
+- ¿Quién es responsable del cálculo?
+- ¿Los valores en caché son suficientemente recientes?
+- ¿Conviene modo normal, read-only o write-only?
+- ¿Los recursos del libro se cierran?
+- ¿Los estilos se reutilizan intencionalmente?
+- ¿Los formatos numéricos están separados de los valores almacenados?
+- ¿Las ediciones estructurales pueden romper referencias?
+- ¿Las reglas de validación son solo UI o validación real de negocio?
+- ¿Debe preservarse VBA?
+- ¿La salida vuelve a abrirse correctamente?
+- ¿Se verifican estructuras críticas después de guardar?
+
+### Ejercicio
+
+Construye un libro ficticio de operaciones mensuales:
+
+1. Crea un `.xlsx` con hojas `Summary` y `Transactions`.
+2. Agrega una fila de encabezado y al menos diez transacciones ficticias.
+3. Usa valores Python `date` o `datetime` explícitos para fechas.
+4. Agrega una fórmula Excel en la hoja de resumen.
+5. Explica por qué la prueba debe verificar el texto de fórmula en vez de esperar que openpyxl la calcule.
+6. Formatea celdas monetarias con number format.
+7. Reutiliza un estilo de encabezado en lugar de inventar formato por celda.
+8. Congela la fila de encabezado de transacciones.
+9. Crea una tabla Excel sobre los datos.
+10. Agrega una validación por lista a una columna de estado.
+11. Guarda en una ruta nueva.
+12. Reabre y verifica nombres de hojas, texto de fórmula, nombre de tabla, cantidad de validaciones y un estilo crítico.
+13. Agrega una función read-only que calcule un total Python a partir de las filas guardadas.
+14. Haz visibles los fallos con contexto útil.
+
+Desafíos adicionales:
+
+- crea un gráfico con los valores del resumen;
+- agrega un nombre definido e inspecciónalo tras recargar;
+- compara diseños de export normal y write-only;
+- procesa un `DataFrame` de pandas y usa openpyxl solo para la capa de presentación;
+- diseña una prueba segura de round-trip `.xlsm` con `keep_vba=True` sin intentar editar el proyecto VBA.
+
+### Conexiones con conceptos anteriores
+
+`openpyxl` se apoya directamente en material previo:
+
+- **funciones y módulos:** aislar generación y validación;
+- **excepciones:** informar entradas malformadas o incompatibles;
+- **`pathlib`:** modelar rutas de origen y destino;
+- **fechas:** almacenar valores temporales Python con formatos de hoja;
+- **`decimal`:** decidir cómo valores monetarios exactos cruzan hacia celdas numéricas de Excel;
+- **`logging`:** registrar rutas, nombres de hojas, conteos y fallos sin ocultar excepciones;
+- **`os` y `shutil`:** descubrir, preparar, copiar y archivar libros con seguridad;
+- **`pandas`:** transformar datos tabulares antes de que openpyxl construya la presentación final.
+
+### Referencias primarias
+
+- [documentación de openpyxl](https://openpyxl.readthedocs.io/)
+- [tutorial de openpyxl](https://openpyxl.readthedocs.io/en/stable/tutorial.html)
+- [Optimised Modes](https://openpyxl.readthedocs.io/en/stable/optimized.html)
+- [Working with styles](https://openpyxl.readthedocs.io/en/stable/styles.html)
+- [Worksheet tables](https://openpyxl.readthedocs.io/en/stable/worksheet_tables.html)
+- [Data validation](https://openpyxl.readthedocs.io/en/stable/validation.html)
+- [Worksheet editing](https://openpyxl.readthedocs.io/en/stable/editing_worksheets.html)
+- [openpyxl en PyPI](https://pypi.org/project/openpyxl/)
+
+Cuando se preparó este capítulo, PyPI listaba openpyxl 3.1.5 como la release estable más reciente. El currículo apunta a la serie 3.1.x en lugar de depender de una versión futura sin límite.
+
+## 67. Próximo capítulo
+
+La Fase 9 ahora tiene dos capas prácticas de datos/libro:
+
+```text
+pandas -> transform tabular data
+openpyxl -> construct and maintain Excel workbooks
+```
+
+La próxima biblioteca planificada es **`requests`**, donde la frontera pasa de archivos locales a servicios HTTP y APIs.
+
+Antes de continuar, practica generando libros que puedas inspeccionar manualmente y validar automáticamente. La automatización de hojas de cálculo se vuelve confiable cuando tanto el contrato de datos como el contrato del workbook son explícitos.
diff --git a/external-libraries/02-openpyxl/README.md b/external-libraries/02-openpyxl/README.md
new file mode 100644
index 0000000..365f9f6
--- /dev/null
+++ b/external-libraries/02-openpyxl/README.md
@@ -0,0 +1,1139 @@
+
+
+# Automating Excel Workbooks with `openpyxl`
+
+[🇺🇸 English](README.md) · [🇧🇷 Português](README.pt-BR.md) · [🇪🇸 Español](README.es.md)
+
+
+
+[← Back to External Libraries](../README.md) · [← Previous: `pandas`](../01-pandas/README.md)
+
+`pandas` treats spreadsheet-like data primarily as tables. `openpyxl` works at a different layer: the Excel workbook itself. It lets Python create, inspect, edit, format, and save Office Open XML workbooks while preserving workbook concepts such as worksheets, cells, formulas, styles, tables, validations, charts, and print settings.
+
+This chapter targets **openpyxl 3.1.x** and was researched against the current 3.1 documentation and the stable **openpyxl 3.1.5** package published on PyPI. PyPI declares Python 3.8 or newer; this repository validates the examples on Python 3.13.
+
+**Estimated study time:** 240–330 minutes.
+
+## Learning goals
+
+By the end of this chapter, you should be able to:
+
+- explain when `openpyxl` is a better fit than `pandas` or the standard `csv` module;
+- create, load, inspect, and save `.xlsx` workbooks;
+- work safely with worksheets, cells, ranges, and row iteration;
+- distinguish formulas from calculated values;
+- understand what `data_only`, `read_only`, `write_only`, and `keep_vba` actually mean;
+- apply reusable styles, number formats, dimensions, and freeze panes;
+- create worksheet tables, validation rules, filters, comments, hyperlinks, and charts;
+- understand the limits of merged cells, row/column movement, formula translation, VBA preservation, and round-trip fidelity;
+- choose optimized modes for large workbooks;
+- treat spreadsheet files as external input with explicit security and validation boundaries;
+- combine `pandas` and `openpyxl` without confusing their responsibilities;
+- build deterministic workbook automation that can be reviewed and tested without Microsoft Excel installed.
+
+## 1. Why `openpyxl` exists
+
+Excel workbooks contain more than rectangular data. They may contain several worksheets, formulas, formatting, tables, validation rules, merged regions, charts, comments, hyperlinks, print settings, and workbook metadata.
+
+`openpyxl` is a third-party Python library for reading and writing Office Open XML spreadsheet files such as `.xlsx` and `.xlsm`.
+
+Use it when the **workbook structure itself matters**.
+
+## 2. `pandas` and `openpyxl` solve different problems
+
+A useful distinction is:
+
+```text
+pandas -> manipulate tabular data
+openpyxl -> manipulate Excel workbook structure
+```
+
+If you need to group ten million rows, `pandas` is usually the stronger abstraction. If you need to set `B2` to a formula, freeze the first row, apply a number format, create a worksheet table, or preserve workbook layout, `openpyxl` is the more natural layer.
+
+Many real workflows use both.
+
+## 3. External libraries require a dependency contract
+
+The repository declares executable Phase 9 dependencies in `requirements-external.txt`.
+
+For this chapter the contract is:
+
+```text
+openpyxl >= 3.1 and < 3.2
+```
+
+Pinning a supported minor series avoids silently teaching against an unknown future API while still allowing compatible patch releases.
+
+## 4. Install the dependency in an isolated environment
+
+Create a virtual environment:
+
+```bash
+python -m venv .venv
+```
+
+Activate it for your operating system, then install the repository contract:
+
+```bash
+python -m pip install -r requirements-external.txt
+```
+
+A direct `pip install openpyxl` is valid for experimentation, but a dependency file makes a project environment reproducible.
+
+## 5. Know the workbook formats in scope
+
+`openpyxl` is designed around Office Open XML workbook formats such as:
+
+```text
+.xlsx
+.xlsm
+.xltx
+.xltm
+```
+
+It is not a general reader for every file Excel can open. In particular, legacy binary `.xls` files and `.xlsb` workbooks are different formats and require different tooling.
+
+Treat the extension as part of your input contract.
+
+## 6. Create a workbook
+
+The central class is `Workbook`:
+
+```python
+from openpyxl import Workbook
+
+
+workbook = Workbook()
+worksheet = workbook.active
+print(worksheet.title)
+```
+
+```text
+Sheet
+```
+
+A normal new workbook begins with one active worksheet.
+
+## 7. Give worksheets meaningful names
+
+Rename the active sheet or create additional sheets explicitly:
+
+```python
+from openpyxl import Workbook
+
+
+workbook = Workbook()
+summary = workbook.active
+summary.title = "Summary"
+details = workbook.create_sheet("Details")
+print(workbook.sheetnames)
+```
+
+```text
+['Summary', 'Details']
+```
+
+Sheet names are part of workbook navigation and may also appear in formulas and defined names.
+
+## 8. Select a worksheet by name
+
+Use mapping-style access:
+
+```python
+from openpyxl import Workbook
+
+
+workbook = Workbook()
+workbook.active.title = "Summary"
+worksheet = workbook["Summary"]
+print(worksheet.title)
+```
+
+Avoid relying on a sheet's physical position when its name is the real contract.
+
+## 9. Cells use Excel-style coordinates
+
+Cells can be accessed with coordinates such as `A1`:
+
+```python
+from openpyxl import Workbook
+
+
+workbook = Workbook()
+worksheet = workbook.active
+worksheet["A1"] = "status"
+worksheet["B1"] = "ready"
+print(worksheet["B1"].value)
+```
+
+```text
+ready
+```
+
+Coordinates are convenient when workbook layout is fixed and meaningful.
+
+## 10. `cell()` uses one-based row and column indexes
+
+Programmatic generation often fits `Worksheet.cell()` better:
+
+```python
+from openpyxl import Workbook
+
+
+workbook = Workbook()
+worksheet = workbook.active
+worksheet.cell(row=2, column=3, value=42)
+print(worksheet["C2"].value)
+```
+
+```text
+42
+```
+
+Excel rows and columns are one-based in this API.
+
+## 11. Accessing cells can create them in memory
+
+A normal worksheet creates cell objects when they are first accessed. That means a loop over an unnecessarily huge coordinate range can allocate many cells even if you never assign useful data.
+
+Do not scan a million-by-million rectangle merely to discover which cells exist.
+
+Use known ranges, worksheet dimensions, or optimized read mode when appropriate.
+
+## 12. Append complete rows
+
+For row-oriented output, `append()` is usually clearer than assigning every coordinate:
+
+```python
+from openpyxl import Workbook
+
+
+workbook = Workbook()
+worksheet = workbook.active
+worksheet.append(["item", "quantity"])
+worksheet.append(["Cable", 3])
+worksheet.append(["Adapter", 2])
+print(worksheet.max_row)
+```
+
+```text
+3
+```
+
+This works well for exports assembled one record at a time.
+
+## 13. Iterate rows instead of hard-coding every cell
+
+`iter_rows()` exposes a rectangular region:
+
+```python
+from openpyxl import Workbook
+
+
+workbook = Workbook()
+worksheet = workbook.active
+worksheet.append(["name", "score"])
+worksheet.append(["A", 8])
+worksheet.append(["B", 9])
+
+for row in worksheet.iter_rows(min_row=2, values_only=True):
+ print(row)
+```
+
+```text
+('A', 8)
+('B', 9)
+```
+
+`values_only=True` returns Python values instead of `Cell` objects when cell metadata is unnecessary.
+
+## 14. Iterate columns only when the access pattern needs them
+
+Normal worksheets also support `iter_cols()`. Row iteration is often more natural for record-like data, while column iteration is useful when a workbook rule is column-oriented.
+
+Optimized read-only mode has a narrower API, so do not design every workflow around methods that are unavailable there.
+
+## 15. Worksheet dimensions are a hint, not a business rule
+
+Properties such as `max_row`, `max_column`, and `calculate_dimension()` describe the worksheet's apparent used region.
+
+They do not prove that every cell inside that region contains meaningful data.
+
+Blank but formatted cells, stale workbook metadata, or third-party generators can make dimensions larger or smaller than expected.
+
+## 16. Save to a new path deliberately
+
+A workbook is persisted with `save()`:
+
+```python
+from pathlib import Path
+from tempfile import TemporaryDirectory
+
+from openpyxl import Workbook
+
+
+with TemporaryDirectory() as temp_dir:
+ path = Path(temp_dir) / "report.xlsx"
+ workbook = Workbook()
+ workbook.active["A1"] = "ready"
+ workbook.save(path)
+ print(path.exists())
+```
+
+```text
+True
+```
+
+For production automation, prefer a deliberate output path over casually overwriting the source workbook.
+
+## 17. Load an existing workbook
+
+Use `load_workbook()`:
+
+```python
+from pathlib import Path
+from tempfile import TemporaryDirectory
+
+from openpyxl import Workbook, load_workbook
+
+
+with TemporaryDirectory() as temp_dir:
+ path = Path(temp_dir) / "input.xlsx"
+ workbook = Workbook()
+ workbook.active["A1"] = "loaded"
+ workbook.save(path)
+
+ reloaded = load_workbook(path)
+ print(reloaded.active["A1"].value)
+ reloaded.close()
+```
+
+```text
+loaded
+```
+
+Closing is especially important for read-only workbooks and is a good explicit habit for file-backed resources.
+
+## 18. A round trip can lose unsupported workbook features
+
+Opening a complex workbook and saving it again is not guaranteed to preserve every artifact created by Excel or another application.
+
+The official tutorial explicitly warns that openpyxl does not read every possible workbook item and that some shapes can be lost during load/save round trips.
+
+Therefore:
+
+```text
+load -> edit one cell -> save
+```
+
+is not automatically a lossless transformation for an arbitrary workbook.
+
+## 19. `read_only=True` is a different operating mode
+
+Large workbooks can consume substantial memory. Read-only mode lazily streams worksheet content:
+
+```python
+from openpyxl import load_workbook
+
+
+workbook = load_workbook("large.xlsx", read_only=True, data_only=True)
+worksheet = workbook["Data"]
+for row in worksheet.iter_rows(values_only=True):
+ process = row
+workbook.close()
+```
+
+The example is intentionally illustrative rather than executable because it depends on an external file.
+
+Read-only worksheets are not normal editable worksheets.
+
+## 20. Read-only mode must be closed explicitly
+
+The official optimized-mode documentation calls out `close()` for read-only workbooks.
+
+Use a `try/finally` boundary when later processing can fail:
+
+```python
+from openpyxl import load_workbook
+
+
+workbook = load_workbook("large.xlsx", read_only=True)
+try:
+ worksheet = workbook.active
+ for row in worksheet.iter_rows(values_only=True):
+ process = row
+finally:
+ workbook.close()
+```
+
+Resource cleanup should survive exceptions.
+
+## 21. Read-only dimensions can be wrong
+
+Lazy reading depends on dimension metadata stored in the workbook. Some producer applications write incorrect dimensions.
+
+The documentation recommends checking `calculate_dimension()` and, when you know the metadata is wrong, using `reset_dimensions()` on a read-only worksheet.
+
+Do this only when you have an external reason to know the stored dimensions are incorrect.
+
+## 22. `write_only=True` is optimized for streaming output
+
+Write-only workbooks are created differently:
+
+```python
+from openpyxl import Workbook
+
+
+workbook = Workbook(write_only=True)
+worksheet = workbook.create_sheet("Data")
+worksheet.append(["id", "value"])
+worksheet.append([1, 10])
+worksheet.append([2, 20])
+```
+
+Unlike a normal `Workbook()`, a write-only workbook starts without a worksheet. You create one explicitly.
+
+## 23. Write-only mode is append-oriented
+
+A write-only worksheet is designed for sequential output. Rows are added with `append()` instead of arbitrary read/write cell access.
+
+This is a strong fit for large exports where records arrive in order and old rows do not need to be edited again.
+
+## 24. A write-only workbook can only be saved once
+
+The optimized-mode documentation states that a write-only workbook can be saved only once.
+
+That means the workflow should be designed as:
+
+```text
+configure workbook -> append rows -> save once
+```
+
+not:
+
+```text
+save -> append more -> save again
+```
+
+Create workbook-level settings that must appear before cell data before you begin streaming rows.
+
+## 25. Choose normal, read-only, and write-only modes intentionally
+
+| Need | Prefer |
+|---|---|
+| edit arbitrary cells | normal workbook |
+| inspect styles, charts, images, and full workbook structure | normal workbook |
+| stream a very large existing worksheet | `read_only=True` |
+| stream a very large new export | `Workbook(write_only=True)` |
+| repeatedly save while editing | normal workbook |
+
+Optimized modes trade capabilities for lower memory use.
+
+## 26. Python values are converted to spreadsheet cell values
+
+Cells can store common Python values such as strings, numbers, booleans, dates, datetimes, and formulas represented as strings beginning with `=`.
+
+Keep your own domain validation separate. The fact that a value can be stored in a cell does not mean it is valid for your application.
+
+## 27. Dates are values plus number formats
+
+Excel stores date/time values with spreadsheet date semantics and displays them using number formats.
+
+When you assign a Python `datetime`, openpyxl applies a date-time-compatible format automatically:
+
+```python
+from datetime import datetime
+
+from openpyxl import Workbook
+
+
+workbook = Workbook()
+worksheet = workbook.active
+worksheet["A1"] = datetime(2026, 8, 29, 14, 30)
+print(worksheet["A1"].is_date)
+```
+
+```text
+True
+```
+
+Do not treat the displayed text in Excel as the only representation that matters.
+
+## 28. Excel has two date systems
+
+Spreadsheet dates may use the 1900 or 1904 date system depending on workbook settings and history.
+
+Let the workbook and openpyxl manage workbook date conversion instead of manually adding a fixed number of days to serial values.
+
+Manual serial arithmetic is an easy way to create off-by-one and epoch errors.
+
+## 29. Formulas are stored as formulas
+
+Assign a formula string beginning with `=`:
+
+```python
+from openpyxl import Workbook
+
+
+workbook = Workbook()
+worksheet = workbook.active
+worksheet["A1"] = 10
+worksheet["A2"] = 20
+worksheet["A3"] = "=SUM(A1:A2)"
+print(worksheet["A3"].value)
+```
+
+```text
+=SUM(A1:A2)
+```
+
+The cell contains a formula expression, not a Python calculation.
+
+## 30. `openpyxl` does not calculate formulas
+
+This is one of the most important boundaries in the library.
+
+`openpyxl` can read and write formula expressions, but it is not an Excel calculation engine. Writing `=SUM(A1:A2)` does not cause openpyxl to compute `30`.
+
+If your Python workflow requires the result now, calculate the value in Python or use a separate calculation engine with a documented contract.
+
+## 31. `data_only=True` reads cached formula results
+
+When loading a workbook, `data_only` controls whether formula cells expose the formula or the value cached the last time a spreadsheet application calculated the workbook.
+
+```text
+load_workbook(path, data_only=False) -> formula text
+load_workbook(path, data_only=True) -> cached result, if available
+```
+
+A newly created workbook may have no cached calculated value at all.
+
+Do not mistake `data_only=True` for “calculate formulas now.”
+
+## 32. Formula names are written in English
+
+The openpyxl formula documentation states that function names must use their English names and arguments use commas.
+
+For example:
+
+```python
+from openpyxl import Workbook
+
+
+workbook = Workbook()
+worksheet = workbook.active
+worksheet["A1"] = "=SUM(1,2,3)"
+print(worksheet["A1"].value)
+```
+
+```text
+=SUM(1,2,3)
+```
+
+Do not generate locale-specific formula syntax from how Excel happens to display formulas on one machine.
+
+## 33. Styles are workbook objects, not display strings
+
+Common style components include:
+
+```text
+Font
+PatternFill / GradientFill
+Border
+Alignment
+Protection
+number_format
+```
+
+The style model is explicit because an Excel cell's appearance is composed from several independent properties.
+
+## 34. Apply a font, fill, and alignment
+
+```python
+from openpyxl import Workbook
+from openpyxl.styles import Alignment, Font, PatternFill
+
+
+workbook = Workbook()
+worksheet = workbook.active
+cell = worksheet["A1"]
+cell.value = "Header"
+cell.font = Font(bold=True)
+cell.fill = PatternFill(fill_type="solid", fgColor="D9EAF7")
+cell.alignment = Alignment(horizontal="center")
+print(cell.font.bold)
+```
+
+```text
+True
+```
+
+Formatting should communicate structure, not compensate for unclear data.
+
+## 35. Cell styles are effectively immutable after assignment
+
+The official styles documentation explains that assigned cell-style components are shared and cannot be mutated in place.
+
+This is intentionally invalid:
+
+```text
+a1.font.italic = True
+```
+
+Assign a new `Font` object instead:
+
+```python
+from openpyxl import Workbook
+from openpyxl.styles import Font
+
+
+workbook = Workbook()
+worksheet = workbook.active
+worksheet["A1"].font = Font(color="FF0000")
+worksheet["A1"].font = Font(color="FF0000", italic=True)
+print(worksheet["A1"].font.italic)
+```
+
+```text
+True
+```
+
+## 36. Reuse style objects instead of creating thousands of variants
+
+If many cells share the same visual role, reuse the same style definition or a `NamedStyle`.
+
+Generating slightly different style objects for every cell can inflate the workbook's style table and file size.
+
+Treat styles as a controlled vocabulary: header, currency, date, warning, input, output.
+
+## 37. Number formats change display, not the underlying value
+
+```python
+from openpyxl import Workbook
+
+
+workbook = Workbook()
+worksheet = workbook.active
+worksheet["A1"] = 0.125
+worksheet["A1"].number_format = "0.00%"
+print(worksheet["A1"].value)
+```
+
+```text
+0.125
+```
+
+Excel may display `12.50%`, but the stored numeric value remains `0.125`.
+
+This distinction matters when another program reads the workbook.
+
+## 38. Named styles make repeated formatting explicit
+
+```python
+from openpyxl import Workbook
+from openpyxl.styles import Font, NamedStyle
+
+
+workbook = Workbook()
+worksheet = workbook.active
+header = NamedStyle(name="header")
+header.font = Font(bold=True)
+workbook.add_named_style(header)
+worksheet["A1"].style = "header"
+print(worksheet["A1"].style)
+```
+
+```text
+header
+```
+
+Once a named style has been assigned to a cell, later changes to the `NamedStyle` do not retroactively restyle that cell.
+
+## 39. Column width and row height are layout metadata
+
+```python
+from openpyxl import Workbook
+
+
+workbook = Workbook()
+worksheet = workbook.active
+worksheet.column_dimensions["A"].width = 24
+worksheet.row_dimensions[1].height = 30
+print(worksheet.column_dimensions["A"].width)
+```
+
+```text
+24.0
+```
+
+Do not assume openpyxl will reproduce Excel's interactive AutoFit behavior merely from content.
+
+## 40. Freeze panes preserve context while scrolling
+
+```python
+from openpyxl import Workbook
+
+
+workbook = Workbook()
+worksheet = workbook.active
+worksheet.freeze_panes = "A2"
+print(worksheet.freeze_panes)
+```
+
+```text
+A2
+```
+
+`A2` freezes rows above row 2, so the first row remains visible.
+
+## 41. Merged cells have one real value cell
+
+When a range is merged, only the top-left cell is the normal value-bearing cell. Other positions become merged-cell placeholders.
+
+```python
+from openpyxl import Workbook
+
+
+workbook = Workbook()
+worksheet = workbook.active
+worksheet.merge_cells("A1:C1")
+worksheet["A1"] = "Quarterly report"
+print(worksheet["A1"].value)
+```
+
+```text
+Quarterly report
+```
+
+Merged cells are presentation structure, not a substitute for normalized tabular data.
+
+## 42. Insert and delete operations do not manage all dependencies
+
+`insert_rows()`, `delete_rows()`, `insert_cols()`, and `delete_cols()` can shift cells.
+
+The official documentation notes that openpyxl does not manage every dependency that may reference the affected cells, such as formulas, tables, or charts.
+
+A structural edit can therefore require application-specific repair logic.
+
+## 43. `move_range()` can translate some formulas, not every reference
+
+`move_range(..., translate=True)` can translate formulas inside the moved cells.
+
+However, references to those cells from other cells or defined names are not automatically updated by that operation.
+
+Do not equate “cells moved” with “workbook semantics repaired.”
+
+## 44. Worksheet tables add Excel table semantics
+
+A worksheet table is more than a colored range. It has a name and a defined cell reference:
+
+```python
+from openpyxl import Workbook
+from openpyxl.worksheet.table import Table
+
+
+workbook = Workbook()
+worksheet = workbook.active
+worksheet.append(["item", "amount"])
+worksheet.append(["A", 10])
+worksheet.append(["B", 20])
+table = Table(displayName="SalesTable", ref="A1:B3")
+worksheet.add_table(table)
+print(list(worksheet.tables.keys()))
+```
+
+```text
+['SalesTable']
+```
+
+Tables are useful when downstream Excel users expect structured references and table-aware formatting.
+
+## 45. Table names and headers are contracts
+
+Table display names must be valid and unique within the relevant workbook namespace. The openpyxl table documentation also requires table column headings to be strings.
+
+Validate headers before constructing a table rather than relying on Excel to repair malformed output later.
+
+## 46. Filters describe workbook behavior; they do not filter Python data
+
+Auto filters can be configured so spreadsheet applications know which rows should be shown under filter criteria.
+
+That is different from filtering records in Python before writing them.
+
+If a report must physically contain only approved rows, filter the Python data first. If users need interactive filtering inside Excel, configure an Excel table or auto filter as presentation behavior.
+
+## 47. Data validation rules are written, not enforced by openpyxl
+
+The official validation documentation is explicit: validators are not enforced or evaluated by openpyxl.
+
+```python
+from openpyxl import Workbook
+from openpyxl.worksheet.datavalidation import DataValidation
+
+
+workbook = Workbook()
+worksheet = workbook.active
+validation = DataValidation(type="list", formula1='"open,closed"')
+worksheet.add_data_validation(validation)
+validation.add("A2:A20")
+print(len(worksheet.data_validations.dataValidation))
+```
+
+```text
+1
+```
+
+The rule becomes workbook metadata that Excel or another compatible application can enforce interactively.
+
+## 48. Conditional formatting is also workbook behavior
+
+Conditional formatting rules tell a spreadsheet application how to format cells when conditions are met.
+
+Do not use conditional formatting as a hidden replacement for data validation. A red cell may communicate an error to a human, but your Python program should still validate critical input explicitly.
+
+## 49. Charts reference worksheet data
+
+`openpyxl.chart` can build charts from worksheet ranges. A typical workflow creates a chart, defines `Reference` objects for data and categories, and anchors the chart on a worksheet.
+
+Charts are presentation objects over worksheet data. Test the underlying numbers separately from the chart layout.
+
+## 50. Images introduce an optional Pillow dependency
+
+The image API can insert raster images into worksheets, but image handling depends on Pillow.
+
+Because this chapter's executable contract does not require images, Pillow is not added merely for a decorative example.
+
+Add optional dependencies only when the project actually needs the feature.
+
+## 51. Comments and hyperlinks are cell metadata
+
+Cells can contain comments and hyperlinks in addition to values and styles.
+
+Use these features when they provide useful human context, but keep essential machine-readable information in normal cells or structured data rather than hiding it in comments.
+
+## 52. Defined names can represent workbook-level references
+
+Excel defined names can point to cells, ranges, constants, or formulas and may have workbook or worksheet scope.
+
+They are useful for workbook contracts, but they also create another dependency layer when cells are moved or sheets are renamed.
+
+Inspect defined names before performing structural edits on complex templates.
+
+## 53. Worksheet protection is not encryption
+
+Cell and worksheet protection controls spreadsheet editing behavior. It is not a substitute for encrypting sensitive files or enforcing server-side authorization.
+
+Treat workbook protection as a user-interface constraint, not a security boundary.
+
+## 54. Print settings are part of the workbook product
+
+Page orientation, margins, print areas, print titles, and scaling can matter when an `.xlsx` file is intended to become a PDF or printed report.
+
+For a data-exchange workbook, these settings may be irrelevant. For a human-facing report, they may be part of the acceptance criteria.
+
+## 55. Understand the important `load_workbook()` flags
+
+Common flags include:
+
+```text
+read_only=True -> lazy, lower-memory reading
+data_only=True -> cached formula results instead of formula text
+keep_vba=True -> preserve VBA content when possible
+keep_links=True -> preserve cached external-link data
+rich_text=True -> preserve rich text formatting in cells
+```
+
+Each flag changes the contract. Do not turn them on merely because they sound safer or more complete.
+
+## 56. `keep_vba=True` preserves VBA; it does not let Python edit it
+
+The official tutorial says VBA elements can be preserved but are still not editable through openpyxl.
+
+If a macro-enabled `.xlsm` workbook must round-trip with VBA intact, use the matching extension and `keep_vba=True`, then test the actual artifact.
+
+Preservation is not execution, inspection, or modification.
+
+## 57. Template and extension mismatches can corrupt expectations
+
+Workbook type, filename extension, and VBA/template settings should agree.
+
+Saving a macro-enabled workbook under the wrong extension or ignoring its VBA contract can produce a file that Excel rejects or that silently loses functionality.
+
+Treat the source and destination workbook types explicitly.
+
+## 58. Untrusted workbooks are a security boundary
+
+An `.xlsx` file is a ZIP package containing XML and related resources. PyPI's openpyxl project page warns that openpyxl does not guard by default against XML quadratic-blowup or billion-laughs attacks and recommends `defusedxml` for protection.
+
+For trusted, repository-generated examples this is not required. For services that accept arbitrary uploaded workbooks, threat modeling and hardened XML parsing belong in the design.
+
+## 59. Invalid files should fail visibly
+
+`load_workbook()` may reject malformed or non-conforming OOXML files.
+
+Catch exceptions only when you can add useful context, then preserve the failure:
+
+```python
+from pathlib import Path
+from zipfile import BadZipFile
+
+from openpyxl import load_workbook
+from openpyxl.utils.exceptions import InvalidFileException
+
+
+def read_sheet_names(path: Path) -> list[str]:
+ try:
+ workbook = load_workbook(path, read_only=True)
+ except (BadZipFile, InvalidFileException, OSError) as exc:
+ raise RuntimeError(f"Could not open workbook: {path.name}") from exc
+
+ try:
+ return workbook.sheetnames
+ finally:
+ workbook.close()
+```
+
+Do not convert every workbook error into an empty report.
+
+## 60. Prefer output validation over “save succeeded”
+
+A successful `save()` proves that bytes were written. It does not prove that the workbook satisfies your business or presentation contract.
+
+Useful post-save checks include:
+
+```text
+file exists
+expected sheet names exist
+required cells contain expected values or formulas
+expected table names exist
+expected validations exist
+critical number formats/styles are present
+workbook reopens successfully
+```
+
+For important templates, open the generated artifact in the target spreadsheet application during acceptance testing too.
+
+## 61. Example: create a workbook and preserve a formula
+
+[`examples/workbook_basics.py`](examples/workbook_basics.py) creates a temporary workbook, adds tabular rows and formulas, saves it, reloads it with formulas visible, and verifies the workbook structure.
+
+Expected output:
+
+```text
+sheet: Summary
+rows: 3
+formula: =B2*C2
+```
+
+The example tests what openpyxl actually owns: formula text and workbook structure, not formula calculation.
+
+## 62. Example: stream rows from a workbook
+
+[`examples/load_and_iterate.py`](examples/load_and_iterate.py) writes a small workbook, reopens it with `read_only=True`, iterates values, and computes a Python total.
+
+Expected output:
+
+```text
+orders: 3
+total: 100.00
+```
+
+This deliberately separates workbook reading from business calculation.
+
+## 63. Example: create a styled report
+
+[`examples/styled_report.py`](examples/styled_report.py) applies a reusable header treatment, number format, freeze pane, and column widths, then reloads the workbook to verify the persisted metadata.
+
+Expected output:
+
+```text
+header bold: True
+number format: #,##0.00
+freeze panes: A2
+```
+
+A deterministic workbook test can inspect metadata without launching Excel.
+
+## 64. Example: tables and validation rules
+
+[`examples/table_and_validation.py`](examples/table_and_validation.py) creates an Excel table and a list validation rule, saves the workbook, reloads it, and verifies that both structures exist.
+
+Expected output:
+
+```text
+tables: ['CatalogTable']
+validations: 1
+```
+
+Remember that the validation rule is stored, not executed, by openpyxl.
+
+## 65. Example: write-only export
+
+[`examples/write_only_export.py`](examples/write_only_export.py) streams rows into a write-only workbook, saves once, then reopens the result in read-only mode for verification.
+
+Expected output:
+
+```text
+rows: 3
+sum: 60
+```
+
+This models the lifecycle of a large sequential export without relying on a large fixture file.
+
+## 66. Common mistakes, decision guide, exercise, and references
+
+Avoid these mistakes:
+
+- using `openpyxl` for heavy table analytics that belong in `pandas`;
+- expecting `.xls` or `.xlsb` support from an `.xlsx` library;
+- assuming `data_only=True` recalculates formulas;
+- overwriting a complex source workbook before verifying round-trip fidelity;
+- treating worksheet dimensions as proof of valid data;
+- using normal mode for huge streaming workloads without considering memory;
+- forgetting to close read-only workbooks;
+- saving a write-only workbook more than once;
+- mutating assigned styles in place;
+- creating thousands of nearly identical style variants;
+- confusing Excel number formats with stored numeric values;
+- assuming row/column insertion repairs formulas, tables, charts, and defined names automatically;
+- expecting data validation to be enforced by openpyxl;
+- treating worksheet protection as security;
+- preserving VBA without testing the `.xlsm` artifact;
+- accepting untrusted workbooks without an XML security strategy;
+- considering `save()` alone sufficient verification.
+
+### Decision table
+
+| Requirement | Prefer |
+|---|---|
+| data filtering/grouping/joining | `pandas` |
+| raw CSV exchange | `csv` or `pandas` |
+| create/edit `.xlsx` workbook structure | `openpyxl` |
+| arbitrary cell edits | normal workbook |
+| large sequential read | `read_only=True` |
+| large sequential write | `Workbook(write_only=True)` |
+| formula text | normal load / `data_only=False` |
+| cached formula value | `data_only=True` |
+| preserve VBA container | `keep_vba=True` + `.xlsm` contract |
+| repeated formatting | reused style objects / `NamedStyle` |
+| interactive Excel validation | `DataValidation` |
+| machine validation | Python validation before writing |
+
+### Quick reference
+
+```text
+from openpyxl import Workbook, load_workbook
+
+wb = Workbook()
+ws = wb.active
+ws = wb["SheetName"]
+wb.create_sheet("Details")
+
+ws["A1"] = "value"
+ws.cell(row=1, column=1, value="value")
+ws.append([...])
+ws.iter_rows(values_only=True)
+
+wb.save(path)
+wb = load_workbook(path)
+wb = load_workbook(path, read_only=True, data_only=True)
+wb.close()
+
+ws.freeze_panes = "A2"
+ws.column_dimensions["A"].width = 20
+ws["B2"].number_format = "#,##0.00"
+
+ws.merge_cells("A1:C1")
+ws.unmerge_cells("A1:C1")
+
+ws.add_table(...)
+ws.add_data_validation(...)
+```
+
+### Design checklist
+
+Before accepting workbook automation, ask:
+
+- What workbook formats are allowed?
+- Is the file trusted or user-supplied?
+- Must unsupported workbook artifacts survive a round trip?
+- Is the source allowed to be overwritten?
+- Which sheets, cells, tables, and names form the contract?
+- Do formulas need formula text or calculated values?
+- Who is responsible for calculation?
+- Are cached formula values fresh enough?
+- Is normal, read-only, or write-only mode appropriate?
+- Are workbook resources closed?
+- Are styles reused intentionally?
+- Are number formats separated from stored values?
+- Can structural edits break references?
+- Are validation rules merely UI behavior or real business validation?
+- Does VBA need preservation?
+- Does the output reopen successfully?
+- Are key workbook structures verified after saving?
+
+### Exercise
+
+Build a fictional monthly operations workbook:
+
+1. Create an `.xlsx` file with `Summary` and `Transactions` sheets.
+2. Add a header row and at least ten fictional transaction rows.
+3. Use explicit Python `date` or `datetime` values for dates.
+4. Add an Excel formula to the summary sheet.
+5. Explain why your test should verify the formula text rather than expect openpyxl to calculate it.
+6. Format money cells with a number format.
+7. Reuse a header style instead of creating unrelated formatting per cell.
+8. Freeze the transaction header row.
+9. Create an Excel table over the transaction data.
+10. Add a list validation rule to a status column.
+11. Save to a new path.
+12. Reopen the workbook and verify required sheet names, formula text, table name, validation count, and one critical style.
+13. Add a read-only inspection function that computes a Python total from the saved rows.
+14. Make failures visible with useful exception context.
+
+Extension challenges:
+
+- create a chart from the summary values;
+- add a defined name and inspect it after reload;
+- compare normal and write-only export designs;
+- process a pandas `DataFrame` and write only the presentation layer with openpyxl;
+- design a safe `.xlsm` round-trip test using `keep_vba=True` without attempting to edit the VBA project.
+
+### Connections to earlier concepts
+
+`openpyxl` builds directly on earlier material:
+
+- **functions and modules:** isolate workbook generation and validation steps;
+- **exceptions:** report malformed or incompatible workbook input;
+- **`pathlib`:** model workbook source and destination paths;
+- **dates:** store Python temporal values with spreadsheet number formats;
+- **`decimal`:** decide explicitly how exact monetary domain values cross into Excel numeric cells;
+- **`logging`:** record workbook paths, sheet names, row counts, and failures without hiding exceptions;
+- **`os` and `shutil`:** discover, stage, copy, and archive workbook files safely;
+- **`pandas`:** transform tabular data before openpyxl builds the final Excel presentation.
+
+### Primary references
+
+- [openpyxl documentation](https://openpyxl.readthedocs.io/)
+- [openpyxl tutorial](https://openpyxl.readthedocs.io/en/stable/tutorial.html)
+- [Optimised Modes](https://openpyxl.readthedocs.io/en/stable/optimized.html)
+- [Working with styles](https://openpyxl.readthedocs.io/en/stable/styles.html)
+- [Worksheet tables](https://openpyxl.readthedocs.io/en/stable/worksheet_tables.html)
+- [Data validation](https://openpyxl.readthedocs.io/en/stable/validation.html)
+- [Worksheet editing](https://openpyxl.readthedocs.io/en/stable/editing_worksheets.html)
+- [openpyxl on PyPI](https://pypi.org/project/openpyxl/)
+
+At the time this chapter was prepared, PyPI listed openpyxl 3.1.5 as the latest stable release. The curriculum targets the 3.1.x series rather than relying on an unbounded future version.
+
+## 67. Next chapter
+
+Phase 9 now has two practical data/workbook layers:
+
+```text
+pandas -> transform tabular data
+openpyxl -> construct and maintain Excel workbooks
+```
+
+The next planned library is **`requests`**, where the boundary moves from local files to HTTP services and APIs.
+
+Before moving on, practice by generating workbooks that you can inspect manually and validate automatically. Spreadsheet automation becomes reliable when both the data contract and the workbook contract are explicit.
diff --git a/external-libraries/02-openpyxl/README.pt-BR.md b/external-libraries/02-openpyxl/README.pt-BR.md
new file mode 100644
index 0000000..0cf3538
--- /dev/null
+++ b/external-libraries/02-openpyxl/README.pt-BR.md
@@ -0,0 +1,1139 @@
+
+
+# Automatizando Workbooks do Excel com `openpyxl`
+
+[🇺🇸 English](README.md) · [🇧🇷 Português](README.pt-BR.md) · [🇪🇸 Español](README.es.md)
+
+
+
+[← Voltar para Bibliotecas Externas](../README.pt-BR.md) · [← Anterior: `pandas`](../01-pandas/README.pt-BR.md)
+
+O `pandas` trata dados semelhantes aos de planilhas principalmente como tabelas. O `openpyxl` atua em outra camada: o próprio workbook do Excel. Ele permite que Python crie, inspecione, edite, formate e salve workbooks Office Open XML preservando conceitos como planilhas, células, fórmulas, estilos, tabelas, validações, gráficos e configurações de impressão.
+
+Este capítulo tem como alvo **openpyxl 3.1.x** e foi pesquisado com base na documentação atual da série 3.1 e no pacote estável **openpyxl 3.1.5** publicado no PyPI. O PyPI declara Python 3.8 ou superior; este repositório valida os exemplos em Python 3.13.
+
+**Tempo estimado de estudo:** 240–330 minutos.
+
+## Objetivos de aprendizagem
+
+Ao final deste capítulo, você deverá ser capaz de:
+
+- explicar quando `openpyxl` é mais adequado do que `pandas` ou o módulo padrão `csv`;
+- criar, carregar, inspecionar e salvar workbooks `.xlsx`;
+- trabalhar com segurança com planilhas, células, intervalos e iteração por linhas;
+- distinguir fórmulas de valores calculados;
+- entender o que `data_only`, `read_only`, `write_only` e `keep_vba` realmente significam;
+- aplicar estilos reutilizáveis, formatos numéricos, dimensões e congelamento de painéis;
+- criar tabelas, regras de validação, filtros, comentários, hyperlinks e gráficos;
+- compreender os limites de células mescladas, movimentação de linhas/colunas, tradução de fórmulas, preservação de VBA e fidelidade de round-trip;
+- escolher modos otimizados para workbooks grandes;
+- tratar arquivos de planilha como entrada externa com limites explícitos de segurança e validação;
+- combinar `pandas` e `openpyxl` sem confundir suas responsabilidades;
+- construir automações determinísticas de workbook que possam ser revisadas e testadas sem Microsoft Excel instalado.
+
+## 1. Por que `openpyxl` existe
+
+Workbooks do Excel contêm mais do que dados retangulares. Eles podem conter várias planilhas, fórmulas, formatação, tabelas, regras de validação, regiões mescladas, gráficos, comentários, hyperlinks, configurações de impressão e metadados do workbook.
+
+`openpyxl` é uma biblioteca Python de terceiros para leitura e escrita de arquivos de planilha Office Open XML, como `.xlsx` e `.xlsm`.
+
+Use-a quando a **estrutura do workbook em si importa**.
+
+## 2. `pandas` e `openpyxl` resolvem problemas diferentes
+
+Uma distinção útil é:
+
+```text
+pandas -> manipulate tabular data
+openpyxl -> manipulate Excel workbook structure
+```
+
+Se você precisa agrupar dez milhões de linhas, `pandas` normalmente oferece uma abstração melhor. Se precisa definir `B2` como fórmula, congelar a primeira linha, aplicar um formato numérico, criar uma tabela do Excel ou preservar o layout do workbook, `openpyxl` é a camada mais natural.
+
+Muitos fluxos reais usam os dois.
+
+## 3. Bibliotecas externas exigem um contrato de dependências
+
+O repositório declara as dependências executáveis da Fase 9 em `requirements-external.txt`.
+
+Para este capítulo, o contrato é:
+
+```text
+openpyxl >= 3.1 and < 3.2
+```
+
+Fixar uma série minor suportada evita ensinar silenciosamente sobre uma API futura desconhecida, ao mesmo tempo em que permite releases de patch compatíveis.
+
+## 4. Instale a dependência em um ambiente isolado
+
+Crie um ambiente virtual:
+
+```bash
+python -m venv .venv
+```
+
+Ative-o conforme seu sistema operacional e instale o contrato do repositório:
+
+```bash
+python -m pip install -r requirements-external.txt
+```
+
+Um `pip install openpyxl` direto é válido para experimentação, mas um arquivo de dependências torna o ambiente do projeto reproduzível.
+
+## 5. Conheça os formatos de workbook no escopo
+
+`openpyxl` foi projetado em torno de formatos Office Open XML como:
+
+```text
+.xlsx
+.xlsm
+.xltx
+.xltm
+```
+
+Ele não é um leitor genérico para todo arquivo que o Excel consegue abrir. Em particular, arquivos binários legados `.xls` e workbooks `.xlsb` são formatos diferentes e exigem outras ferramentas.
+
+Trate a extensão como parte do contrato de entrada.
+
+## 6. Crie um workbook
+
+A classe central é `Workbook`:
+
+```python
+from openpyxl import Workbook
+
+
+workbook = Workbook()
+worksheet = workbook.active
+print(worksheet.title)
+```
+
+```text
+Sheet
+```
+
+Um novo workbook normal começa com uma planilha ativa.
+
+## 7. Dê nomes significativos às planilhas
+
+Renomeie a planilha ativa ou crie planilhas adicionais explicitamente:
+
+```python
+from openpyxl import Workbook
+
+
+workbook = Workbook()
+summary = workbook.active
+summary.title = "Summary"
+details = workbook.create_sheet("Details")
+print(workbook.sheetnames)
+```
+
+```text
+['Summary', 'Details']
+```
+
+Nomes de planilhas fazem parte da navegação do workbook e também podem aparecer em fórmulas e nomes definidos.
+
+## 8. Selecione uma planilha pelo nome
+
+Use acesso semelhante a um mapeamento:
+
+```python
+from openpyxl import Workbook
+
+
+workbook = Workbook()
+workbook.active.title = "Summary"
+worksheet = workbook["Summary"]
+print(worksheet.title)
+```
+
+Evite depender da posição física de uma planilha quando o nome é o contrato real.
+
+## 9. Células usam coordenadas no estilo Excel
+
+Células podem ser acessadas com coordenadas como `A1`:
+
+```python
+from openpyxl import Workbook
+
+
+workbook = Workbook()
+worksheet = workbook.active
+worksheet["A1"] = "status"
+worksheet["B1"] = "ready"
+print(worksheet["B1"].value)
+```
+
+```text
+ready
+```
+
+Coordenadas são convenientes quando o layout do workbook é fixo e significativo.
+
+## 10. `cell()` usa índices de linha e coluna iniciando em um
+
+Geração programática costuma combinar melhor com `Worksheet.cell()`:
+
+```python
+from openpyxl import Workbook
+
+
+workbook = Workbook()
+worksheet = workbook.active
+worksheet.cell(row=2, column=3, value=42)
+print(worksheet["C2"].value)
+```
+
+```text
+42
+```
+
+Linhas e colunas do Excel são indexadas a partir de um nessa API.
+
+## 11. Acessar células pode criá-las em memória
+
+Uma planilha normal cria objetos de célula quando eles são acessados pela primeira vez. Isso significa que um loop sobre um intervalo de coordenadas enorme e desnecessário pode alocar muitas células mesmo sem atribuir dados úteis.
+
+Não percorra um retângulo de um milhão por um milhão apenas para descobrir quais células existem.
+
+Use intervalos conhecidos, dimensões da planilha ou modo de leitura otimizado quando apropriado.
+
+## 12. Adicione linhas completas com `append()`
+
+Para saída orientada a linhas, `append()` costuma ser mais claro do que atribuir cada coordenada:
+
+```python
+from openpyxl import Workbook
+
+
+workbook = Workbook()
+worksheet = workbook.active
+worksheet.append(["item", "quantity"])
+worksheet.append(["Cable", 3])
+worksheet.append(["Adapter", 2])
+print(worksheet.max_row)
+```
+
+```text
+3
+```
+
+Isso funciona bem para exports montados registro por registro.
+
+## 13. Itere linhas em vez de codificar cada célula
+
+`iter_rows()` expõe uma região retangular:
+
+```python
+from openpyxl import Workbook
+
+
+workbook = Workbook()
+worksheet = workbook.active
+worksheet.append(["name", "score"])
+worksheet.append(["A", 8])
+worksheet.append(["B", 9])
+
+for row in worksheet.iter_rows(min_row=2, values_only=True):
+ print(row)
+```
+
+```text
+('A', 8)
+('B', 9)
+```
+
+`values_only=True` retorna valores Python em vez de objetos `Cell` quando os metadados da célula não são necessários.
+
+## 14. Itere colunas apenas quando o padrão de acesso exigir
+
+Planilhas normais também suportam `iter_cols()`. A iteração por linhas costuma ser mais natural para dados semelhantes a registros, enquanto a iteração por colunas é útil quando a regra do workbook é orientada a coluna.
+
+O modo otimizado read-only possui uma API mais restrita, então não projete todo fluxo em torno de métodos indisponíveis nele.
+
+## 15. Dimensões da planilha são uma pista, não uma regra de negócio
+
+Propriedades como `max_row`, `max_column` e `calculate_dimension()` descrevem a região aparentemente usada da planilha.
+
+Elas não provam que cada célula dentro dessa região contém dados significativos.
+
+Células vazias porém formatadas, metadados antigos ou geradores de terceiros podem tornar as dimensões maiores ou menores do que o esperado.
+
+## 16. Salve deliberadamente em um novo caminho
+
+Um workbook é persistido com `save()`:
+
+```python
+from pathlib import Path
+from tempfile import TemporaryDirectory
+
+from openpyxl import Workbook
+
+
+with TemporaryDirectory() as temp_dir:
+ path = Path(temp_dir) / "report.xlsx"
+ workbook = Workbook()
+ workbook.active["A1"] = "ready"
+ workbook.save(path)
+ print(path.exists())
+```
+
+```text
+True
+```
+
+Em automações de produção, prefira um caminho de saída deliberado a sobrescrever casualmente o workbook de origem.
+
+## 17. Carregue um workbook existente
+
+Use `load_workbook()`:
+
+```python
+from pathlib import Path
+from tempfile import TemporaryDirectory
+
+from openpyxl import Workbook, load_workbook
+
+
+with TemporaryDirectory() as temp_dir:
+ path = Path(temp_dir) / "input.xlsx"
+ workbook = Workbook()
+ workbook.active["A1"] = "loaded"
+ workbook.save(path)
+
+ reloaded = load_workbook(path)
+ print(reloaded.active["A1"].value)
+ reloaded.close()
+```
+
+```text
+loaded
+```
+
+Fechar explicitamente é especialmente importante em workbooks read-only e é um bom hábito para recursos associados a arquivos.
+
+## 18. Um round-trip pode perder recursos não suportados
+
+Abrir um workbook complexo e salvá-lo novamente não garante a preservação de todo artefato criado pelo Excel ou por outro aplicativo.
+
+O tutorial oficial alerta explicitamente que openpyxl não lê todos os itens possíveis de um workbook e que algumas shapes podem ser perdidas durante um round-trip de load/save.
+
+Portanto:
+
+```text
+load -> edit one cell -> save
+```
+
+não é automaticamente uma transformação sem perdas para qualquer workbook.
+
+## 19. `read_only=True` é um modo de operação diferente
+
+Workbooks grandes podem consumir muita memória. O modo read-only carrega o conteúdo da planilha de forma lazy:
+
+```python
+from openpyxl import load_workbook
+
+
+workbook = load_workbook("large.xlsx", read_only=True, data_only=True)
+worksheet = workbook["Data"]
+for row in worksheet.iter_rows(values_only=True):
+ process = row
+workbook.close()
+```
+
+O exemplo é intencionalmente ilustrativo, e não executável no repositório, porque depende de um arquivo externo.
+
+Planilhas read-only não são planilhas normais editáveis.
+
+## 20. O modo read-only deve ser fechado explicitamente
+
+A documentação oficial dos modos otimizados destaca `close()` para workbooks read-only.
+
+Use uma fronteira `try/finally` quando o processamento posterior puder falhar:
+
+```python
+from openpyxl import load_workbook
+
+
+workbook = load_workbook("large.xlsx", read_only=True)
+try:
+ worksheet = workbook.active
+ for row in worksheet.iter_rows(values_only=True):
+ process = row
+finally:
+ workbook.close()
+```
+
+A liberação do recurso deve sobreviver a exceções.
+
+## 21. Dimensões em read-only podem estar incorretas
+
+A leitura lazy depende dos metadados de dimensões armazenados no workbook. Alguns aplicativos produtores gravam essas dimensões incorretamente.
+
+A documentação recomenda verificar `calculate_dimension()` e, quando você sabe que o metadado está errado, usar `reset_dimensions()` em uma planilha read-only.
+
+Faça isso somente quando houver um motivo externo para saber que as dimensões gravadas estão incorretas.
+
+## 22. `write_only=True` é otimizado para saída em streaming
+
+Workbooks write-only são criados de forma diferente:
+
+```python
+from openpyxl import Workbook
+
+
+workbook = Workbook(write_only=True)
+worksheet = workbook.create_sheet("Data")
+worksheet.append(["id", "value"])
+worksheet.append([1, 10])
+worksheet.append([2, 20])
+```
+
+Diferente de `Workbook()` normal, um workbook write-only começa sem planilha. Você precisa criar uma explicitamente.
+
+## 23. O modo write-only é orientado a `append()`
+
+Uma planilha write-only foi desenhada para saída sequencial. Linhas são adicionadas com `append()` em vez de leitura e escrita arbitrária de células.
+
+Isso combina bem com exports grandes em que registros chegam em ordem e linhas anteriores não precisam ser editadas novamente.
+
+## 24. Um workbook write-only só pode ser salvo uma vez
+
+A documentação dos modos otimizados informa que um workbook write-only pode ser salvo apenas uma vez.
+
+Portanto, o fluxo deve ser:
+
+```text
+configure workbook -> append rows -> save once
+```
+
+e não:
+
+```text
+save -> append more -> save again
+```
+
+Crie configurações de workbook que precisam existir antes dos dados antes de iniciar o streaming de linhas.
+
+## 25. Escolha conscientemente entre normal, read-only e write-only
+
+| Necessidade | Prefira |
+|---|---|
+| editar células arbitrárias | workbook normal |
+| inspecionar estilos, gráficos, imagens e estrutura completa | workbook normal |
+| fazer streaming de planilha existente muito grande | `read_only=True` |
+| fazer streaming de novo export muito grande | `Workbook(write_only=True)` |
+| salvar repetidamente durante edição | workbook normal |
+
+Modos otimizados trocam recursos por menor uso de memória.
+
+## 26. Valores Python são convertidos para valores de célula
+
+Células podem armazenar valores Python comuns como strings, números, booleanos, datas, datetimes e fórmulas representadas por strings iniciadas com `=`.
+
+Mantenha sua validação de domínio separada. O fato de um valor poder ser gravado em uma célula não significa que ele seja válido para sua aplicação.
+
+## 27. Datas são valores mais formatos numéricos
+
+Excel armazena valores de data/hora com semântica de data de planilha e os exibe por meio de formatos numéricos.
+
+Ao atribuir um `datetime` Python, openpyxl aplica automaticamente um formato compatível com data/hora:
+
+```python
+from datetime import datetime
+
+from openpyxl import Workbook
+
+
+workbook = Workbook()
+worksheet = workbook.active
+worksheet["A1"] = datetime(2026, 8, 29, 14, 30)
+print(worksheet["A1"].is_date)
+```
+
+```text
+True
+```
+
+Não trate o texto exibido no Excel como a única representação relevante.
+
+## 28. Excel possui dois sistemas de data
+
+Datas de planilhas podem usar o sistema 1900 ou 1904, dependendo das configurações e do histórico do workbook.
+
+Deixe o workbook e openpyxl gerenciarem a conversão em vez de adicionar manualmente um número fixo de dias a valores seriais.
+
+Aritmética manual de serial é uma forma fácil de criar erros de época e deslocamento.
+
+## 29. Fórmulas são armazenadas como fórmulas
+
+Atribua uma string de fórmula iniciada por `=`:
+
+```python
+from openpyxl import Workbook
+
+
+workbook = Workbook()
+worksheet = workbook.active
+worksheet["A1"] = 10
+worksheet["A2"] = 20
+worksheet["A3"] = "=SUM(A1:A2)"
+print(worksheet["A3"].value)
+```
+
+```text
+=SUM(A1:A2)
+```
+
+A célula contém uma expressão de fórmula, não um cálculo Python.
+
+## 30. `openpyxl` não calcula fórmulas
+
+Esta é uma das fronteiras mais importantes da biblioteca.
+
+`openpyxl` consegue ler e gravar expressões de fórmula, mas não é um motor de cálculo do Excel. Gravar `=SUM(A1:A2)` não faz openpyxl calcular `30`.
+
+Se seu fluxo Python precisa do resultado naquele momento, calcule o valor em Python ou use um motor de cálculo separado com contrato documentado.
+
+## 31. `data_only=True` lê resultados em cache
+
+Ao carregar um workbook, `data_only` controla se células com fórmula expõem a fórmula ou o valor em cache deixado pela última aplicação de planilha que calculou o arquivo.
+
+```text
+load_workbook(path, data_only=False) -> formula text
+load_workbook(path, data_only=True) -> cached result, if available
+```
+
+Um workbook recém-criado pode não possuir nenhum valor calculado em cache.
+
+Não confunda `data_only=True` com “calcular fórmulas agora”.
+
+## 32. Nomes de fórmulas são gravados em inglês
+
+A documentação de fórmulas do openpyxl informa que os nomes das funções devem estar em inglês e os argumentos usam vírgulas.
+
+Por exemplo:
+
+```python
+from openpyxl import Workbook
+
+
+workbook = Workbook()
+worksheet = workbook.active
+worksheet["A1"] = "=SUM(1,2,3)"
+print(worksheet["A1"].value)
+```
+
+```text
+=SUM(1,2,3)
+```
+
+Não gere sintaxe de fórmula específica de idioma com base em como o Excel mostra fórmulas em uma máquina.
+
+## 33. Estilos são objetos do workbook, não strings de aparência
+
+Componentes comuns de estilo incluem:
+
+```text
+Font
+PatternFill / GradientFill
+Border
+Alignment
+Protection
+number_format
+```
+
+O modelo é explícito porque a aparência de uma célula do Excel é composta por várias propriedades independentes.
+
+## 34. Aplique fonte, preenchimento e alinhamento
+
+```python
+from openpyxl import Workbook
+from openpyxl.styles import Alignment, Font, PatternFill
+
+
+workbook = Workbook()
+worksheet = workbook.active
+cell = worksheet["A1"]
+cell.value = "Header"
+cell.font = Font(bold=True)
+cell.fill = PatternFill(fill_type="solid", fgColor="D9EAF7")
+cell.alignment = Alignment(horizontal="center")
+print(cell.font.bold)
+```
+
+```text
+True
+```
+
+Formatação deve comunicar estrutura, não compensar dados pouco claros.
+
+## 35. Estilos de célula são efetivamente imutáveis após atribuição
+
+A documentação oficial de estilos explica que componentes atribuídos são compartilhados e não podem ser alterados in-place.
+
+Isto é intencionalmente inválido:
+
+```text
+a1.font.italic = True
+```
+
+Atribua um novo objeto `Font`:
+
+```python
+from openpyxl import Workbook
+from openpyxl.styles import Font
+
+
+workbook = Workbook()
+worksheet = workbook.active
+worksheet["A1"].font = Font(color="FF0000")
+worksheet["A1"].font = Font(color="FF0000", italic=True)
+print(worksheet["A1"].font.italic)
+```
+
+```text
+True
+```
+
+## 36. Reutilize objetos de estilo em vez de criar milhares de variações
+
+Se muitas células compartilham o mesmo papel visual, reutilize a mesma definição de estilo ou um `NamedStyle`.
+
+Criar objetos levemente diferentes para cada célula pode inflar a tabela de estilos e o tamanho do arquivo.
+
+Trate estilos como um vocabulário controlado: cabeçalho, moeda, data, alerta, entrada, saída.
+
+## 37. Formatos numéricos mudam a exibição, não o valor armazenado
+
+```python
+from openpyxl import Workbook
+
+
+workbook = Workbook()
+worksheet = workbook.active
+worksheet["A1"] = 0.125
+worksheet["A1"].number_format = "0.00%"
+print(worksheet["A1"].value)
+```
+
+```text
+0.125
+```
+
+O Excel pode mostrar `12.50%`, mas o valor numérico armazenado continua sendo `0.125`.
+
+Essa diferença importa quando outro programa lê o workbook.
+
+## 38. Named styles tornam formatação repetida explícita
+
+```python
+from openpyxl import Workbook
+from openpyxl.styles import Font, NamedStyle
+
+
+workbook = Workbook()
+worksheet = workbook.active
+header = NamedStyle(name="header")
+header.font = Font(bold=True)
+workbook.add_named_style(header)
+worksheet["A1"].style = "header"
+print(worksheet["A1"].style)
+```
+
+```text
+header
+```
+
+Depois que um named style é atribuído a uma célula, alterações posteriores no `NamedStyle` não reestilizam retroativamente essa célula.
+
+## 39. Largura de coluna e altura de linha são metadados de layout
+
+```python
+from openpyxl import Workbook
+
+
+workbook = Workbook()
+worksheet = workbook.active
+worksheet.column_dimensions["A"].width = 24
+worksheet.row_dimensions[1].height = 30
+print(worksheet.column_dimensions["A"].width)
+```
+
+```text
+24.0
+```
+
+Não presuma que openpyxl reproduzirá o AutoFit interativo do Excel apenas com base no conteúdo.
+
+## 40. Freeze panes preserva contexto durante a rolagem
+
+```python
+from openpyxl import Workbook
+
+
+workbook = Workbook()
+worksheet = workbook.active
+worksheet.freeze_panes = "A2"
+print(worksheet.freeze_panes)
+```
+
+```text
+A2
+```
+
+`A2` congela as linhas acima da linha 2, mantendo a primeira linha visível.
+
+## 41. Células mescladas possuem uma única célula real de valor
+
+Quando um intervalo é mesclado, apenas a célula superior esquerda é a célula normal que carrega valor. As outras posições se tornam placeholders de merged cell.
+
+```python
+from openpyxl import Workbook
+
+
+workbook = Workbook()
+worksheet = workbook.active
+worksheet.merge_cells("A1:C1")
+worksheet["A1"] = "Quarterly report"
+print(worksheet["A1"].value)
+```
+
+```text
+Quarterly report
+```
+
+Células mescladas são estrutura de apresentação, não substituto para dados tabulares normalizados.
+
+## 42. Inserções e exclusões não gerenciam todas as dependências
+
+`insert_rows()`, `delete_rows()`, `insert_cols()` e `delete_cols()` podem deslocar células.
+
+A documentação oficial observa que openpyxl não gerencia toda dependência que possa referenciar as células afetadas, como fórmulas, tabelas ou gráficos.
+
+Uma edição estrutural pode, portanto, exigir lógica específica da aplicação para reparar referências.
+
+## 43. `move_range()` pode traduzir algumas fórmulas, não toda referência
+
+`move_range(..., translate=True)` pode traduzir fórmulas dentro das células movidas.
+
+Porém, referências a essas células vindas de outras células ou de nomes definidos não são atualizadas automaticamente por essa operação.
+
+Não confunda “células movidas” com “semântica do workbook reparada”.
+
+## 44. Tabelas de planilha adicionam semântica de tabela do Excel
+
+Uma worksheet table é mais do que um intervalo colorido. Ela possui nome e referência de células definida:
+
+```python
+from openpyxl import Workbook
+from openpyxl.worksheet.table import Table
+
+
+workbook = Workbook()
+worksheet = workbook.active
+worksheet.append(["item", "amount"])
+worksheet.append(["A", 10])
+worksheet.append(["B", 20])
+table = Table(displayName="SalesTable", ref="A1:B3")
+worksheet.add_table(table)
+print(list(worksheet.tables.keys()))
+```
+
+```text
+['SalesTable']
+```
+
+Tabelas são úteis quando usuários posteriores do Excel esperam referências estruturadas e formatação consciente de tabela.
+
+## 45. Nomes e cabeçalhos de tabela são contratos
+
+Display names de tabela precisam ser válidos e únicos no namespace relevante do workbook. A documentação também exige que os cabeçalhos das colunas sejam strings.
+
+Valide cabeçalhos antes de construir a tabela em vez de depender do Excel para reparar uma saída malformada depois.
+
+## 46. Filtros descrevem comportamento do workbook; não filtram dados Python
+
+Auto filters podem ser configurados para que aplicações de planilha saibam quais linhas exibir sob certos critérios.
+
+Isso é diferente de filtrar registros em Python antes de gravá-los.
+
+Se um relatório deve conter fisicamente apenas linhas aprovadas, filtre os dados Python primeiro. Se usuários precisam de filtragem interativa no Excel, configure uma tabela ou auto filter como comportamento de apresentação.
+
+## 47. Regras de validação são gravadas, não executadas por openpyxl
+
+A documentação oficial de validação é explícita: validadores não são aplicados nem avaliados por openpyxl.
+
+```python
+from openpyxl import Workbook
+from openpyxl.worksheet.datavalidation import DataValidation
+
+
+workbook = Workbook()
+worksheet = workbook.active
+validation = DataValidation(type="list", formula1='"open,closed"')
+worksheet.add_data_validation(validation)
+validation.add("A2:A20")
+print(len(worksheet.data_validations.dataValidation))
+```
+
+```text
+1
+```
+
+A regra se torna metadado do workbook que o Excel ou outra aplicação compatível pode aplicar interativamente.
+
+## 48. Formatação condicional também é comportamento do workbook
+
+Regras de conditional formatting dizem a uma aplicação de planilha como formatar células quando condições são atendidas.
+
+Não use formatação condicional como substituto oculto para validação de dados. Uma célula vermelha pode comunicar um erro a uma pessoa, mas o programa Python ainda deve validar entradas críticas explicitamente.
+
+## 49. Gráficos referenciam dados da planilha
+
+`openpyxl.chart` pode construir gráficos a partir de intervalos. Um fluxo típico cria um chart, define objetos `Reference` para dados e categorias e ancora o gráfico em uma planilha.
+
+Gráficos são objetos de apresentação sobre dados da planilha. Teste os números subjacentes separadamente do layout do gráfico.
+
+## 50. Imagens introduzem uma dependência opcional de Pillow
+
+A API de imagens pode inserir imagens raster em planilhas, mas o tratamento de imagens depende de Pillow.
+
+Como o contrato executável deste capítulo não exige imagens, Pillow não é adicionada apenas por causa de um exemplo decorativo.
+
+Adicione dependências opcionais somente quando o projeto realmente precisa da funcionalidade.
+
+## 51. Comentários e hyperlinks são metadados de célula
+
+Células podem conter comentários e hyperlinks além de valores e estilos.
+
+Use esses recursos quando oferecerem contexto útil para humanos, mas mantenha informações essenciais legíveis por máquina em células normais ou dados estruturados em vez de escondê-las em comentários.
+
+## 52. Nomes definidos podem representar referências no nível do workbook
+
+Defined names do Excel podem apontar para células, intervalos, constantes ou fórmulas e podem ter escopo do workbook ou da planilha.
+
+Eles são úteis para contratos de workbook, mas também criam outra camada de dependência quando células são movidas ou planilhas renomeadas.
+
+Inspecione nomes definidos antes de realizar edições estruturais em templates complexos.
+
+## 53. Proteção de planilha não é criptografia
+
+Proteção de célula e worksheet controla comportamento de edição na planilha. Não substitui criptografia de arquivos sensíveis nem autorização no servidor.
+
+Trate proteção do workbook como uma restrição de interface, não como limite de segurança.
+
+## 54. Configurações de impressão fazem parte do produto workbook
+
+Orientação de página, margens, áreas de impressão, títulos repetidos e escala podem importar quando um `.xlsx` deve virar PDF ou relatório impresso.
+
+Para um workbook de troca de dados, isso pode ser irrelevante. Para um relatório voltado a pessoas, pode fazer parte dos critérios de aceite.
+
+## 55. Entenda as flags importantes de `load_workbook()`
+
+Flags comuns incluem:
+
+```text
+read_only=True -> lazy, lower-memory reading
+data_only=True -> cached formula results instead of formula text
+keep_vba=True -> preserve VBA content when possible
+keep_links=True -> preserve cached external-link data
+rich_text=True -> preserve rich text formatting in cells
+```
+
+Cada flag muda o contrato. Não as ative apenas porque parecem mais seguras ou completas.
+
+## 56. `keep_vba=True` preserva VBA; não permite editá-lo
+
+O tutorial oficial informa que elementos VBA podem ser preservados, mas continuam não editáveis pelo openpyxl.
+
+Se um `.xlsm` com macros precisa fazer round-trip preservando VBA, use a extensão correspondente e `keep_vba=True`, e teste o artefato real.
+
+Preservação não significa execução, inspeção ou modificação.
+
+## 57. Incompatibilidades entre template e extensão podem quebrar expectativas
+
+Tipo do workbook, extensão do arquivo e configurações de VBA/template devem estar alinhados.
+
+Salvar um workbook com macros usando a extensão errada ou ignorar seu contrato de VBA pode produzir um arquivo rejeitado pelo Excel ou que perde funcionalidade silenciosamente.
+
+Trate explicitamente os tipos de origem e destino.
+
+## 58. Workbooks não confiáveis são uma fronteira de segurança
+
+Um `.xlsx` é um pacote ZIP contendo XML e recursos relacionados. A página do projeto openpyxl no PyPI alerta que openpyxl não protege por padrão contra ataques XML de quadratic blowup ou billion laughs e recomenda `defusedxml` como proteção.
+
+Para exemplos confiáveis gerados pelo próprio repositório, isso não é necessário. Para serviços que aceitam workbooks arbitrários enviados por usuários, modelagem de ameaça e parsing XML endurecido fazem parte do design.
+
+## 59. Arquivos inválidos devem falhar de forma visível
+
+`load_workbook()` pode rejeitar arquivos OOXML malformados ou incompatíveis.
+
+Capture exceções apenas quando puder acrescentar contexto útil e preserve a falha:
+
+```python
+from pathlib import Path
+from zipfile import BadZipFile
+
+from openpyxl import load_workbook
+from openpyxl.utils.exceptions import InvalidFileException
+
+
+def read_sheet_names(path: Path) -> list[str]:
+ try:
+ workbook = load_workbook(path, read_only=True)
+ except (BadZipFile, InvalidFileException, OSError) as exc:
+ raise RuntimeError(f"Could not open workbook: {path.name}") from exc
+
+ try:
+ return workbook.sheetnames
+ finally:
+ workbook.close()
+```
+
+Não transforme todo erro de workbook em um relatório vazio.
+
+## 60. Prefira validar a saída em vez de aceitar apenas “save funcionou”
+
+Um `save()` bem-sucedido prova que bytes foram gravados. Não prova que o workbook atende ao contrato de negócio ou apresentação.
+
+Verificações úteis após salvar incluem:
+
+```text
+file exists
+expected sheet names exist
+required cells contain expected values or formulas
+expected table names exist
+expected validations exist
+critical number formats/styles are present
+workbook reopens successfully
+```
+
+Para templates importantes, abra o artefato gerado na aplicação de planilha de destino durante testes de aceite também.
+
+## 61. Exemplo: crie um workbook e preserve uma fórmula
+
+[`examples/workbook_basics.py`](examples/workbook_basics.py) cria um workbook temporário, adiciona linhas tabulares e fórmulas, salva, recarrega com fórmulas visíveis e verifica a estrutura.
+
+Saída esperada:
+
+```text
+sheet: Summary
+rows: 3
+formula: =B2*C2
+```
+
+O exemplo testa aquilo que openpyxl realmente controla: texto da fórmula e estrutura do workbook, não o cálculo da fórmula.
+
+## 62. Exemplo: faça streaming de linhas de um workbook
+
+[`examples/load_and_iterate.py`](examples/load_and_iterate.py) grava um workbook pequeno, reabre com `read_only=True`, itera valores e calcula um total em Python.
+
+Saída esperada:
+
+```text
+orders: 3
+total: 100.00
+```
+
+Isso separa deliberadamente a leitura do workbook do cálculo de negócio.
+
+## 63. Exemplo: crie um relatório formatado
+
+[`examples/styled_report.py`](examples/styled_report.py) aplica tratamento reutilizável de cabeçalho, formato numérico, freeze pane e larguras de coluna, depois recarrega o workbook para verificar os metadados persistidos.
+
+Saída esperada:
+
+```text
+header bold: True
+number format: #,##0.00
+freeze panes: A2
+```
+
+Um teste determinístico de workbook pode inspecionar metadados sem iniciar o Excel.
+
+## 64. Exemplo: tabelas e regras de validação
+
+[`examples/table_and_validation.py`](examples/table_and_validation.py) cria uma tabela do Excel e uma regra de validação por lista, salva, recarrega e verifica se ambas as estruturas existem.
+
+Saída esperada:
+
+```text
+tables: ['CatalogTable']
+validations: 1
+```
+
+Lembre que a regra de validação é armazenada, não executada, por openpyxl.
+
+## 65. Exemplo: export write-only
+
+[`examples/write_only_export.py`](examples/write_only_export.py) faz streaming de linhas para um workbook write-only, salva uma única vez e depois reabre o resultado em modo read-only para verificação.
+
+Saída esperada:
+
+```text
+rows: 3
+sum: 60
+```
+
+Isso modela o ciclo de vida de um export sequencial grande sem depender de um fixture enorme.
+
+## 66. Erros comuns, guia de decisão, exercício e referências
+
+Evite estes erros:
+
+- usar `openpyxl` para análise tabular pesada que pertence ao `pandas`;
+- esperar suporte a `.xls` ou `.xlsb` de uma biblioteca de `.xlsx`;
+- supor que `data_only=True` recalcula fórmulas;
+- sobrescrever um workbook de origem complexo antes de verificar a fidelidade do round-trip;
+- tratar dimensões da planilha como prova de dados válidos;
+- usar modo normal para cargas enormes sem considerar memória;
+- esquecer de fechar workbooks read-only;
+- salvar um workbook write-only mais de uma vez;
+- tentar alterar estilos atribuídos in-place;
+- criar milhares de variantes quase idênticas de estilo;
+- confundir formatos numéricos do Excel com valores armazenados;
+- supor que inserção de linhas/colunas repara fórmulas, tabelas, gráficos e nomes definidos automaticamente;
+- esperar que data validation seja executada por openpyxl;
+- tratar proteção de planilha como segurança;
+- preservar VBA sem testar o artefato `.xlsm`;
+- aceitar workbooks não confiáveis sem estratégia de segurança XML;
+- considerar `save()` sozinho como verificação suficiente.
+
+### Tabela de decisão
+
+| Necessidade | Prefira |
+|---|---|
+| filtrar/agrupar/juntar dados | `pandas` |
+| troca simples via CSV | `csv` ou `pandas` |
+| criar/editar estrutura `.xlsx` | `openpyxl` |
+| editar células arbitrárias | workbook normal |
+| leitura sequencial grande | `read_only=True` |
+| escrita sequencial grande | `Workbook(write_only=True)` |
+| texto da fórmula | carga normal / `data_only=False` |
+| valor de fórmula em cache | `data_only=True` |
+| preservar container VBA | `keep_vba=True` + contrato `.xlsm` |
+| formatação repetida | objetos de estilo reutilizados / `NamedStyle` |
+| validação interativa no Excel | `DataValidation` |
+| validação de máquina | validação Python antes da escrita |
+
+### Referência rápida
+
+```text
+from openpyxl import Workbook, load_workbook
+
+wb = Workbook()
+ws = wb.active
+ws = wb["SheetName"]
+wb.create_sheet("Details")
+
+ws["A1"] = "value"
+ws.cell(row=1, column=1, value="value")
+ws.append([...])
+ws.iter_rows(values_only=True)
+
+wb.save(path)
+wb = load_workbook(path)
+wb = load_workbook(path, read_only=True, data_only=True)
+wb.close()
+
+ws.freeze_panes = "A2"
+ws.column_dimensions["A"].width = 20
+ws["B2"].number_format = "#,##0.00"
+
+ws.merge_cells("A1:C1")
+ws.unmerge_cells("A1:C1")
+
+ws.add_table(...)
+ws.add_data_validation(...)
+```
+
+### Checklist de design
+
+Antes de aceitar uma automação de workbook, pergunte:
+
+- Quais formatos de workbook são permitidos?
+- O arquivo é confiável ou enviado por usuário?
+- Recursos não suportados precisam sobreviver ao round-trip?
+- A origem pode ser sobrescrita?
+- Quais planilhas, células, tabelas e nomes formam o contrato?
+- Fórmulas precisam do texto ou do valor calculado?
+- Quem é responsável pelo cálculo?
+- Valores de fórmula em cache são recentes o suficiente?
+- Modo normal, read-only ou write-only é apropriado?
+- Recursos do workbook são fechados?
+- Estilos são reutilizados de forma intencional?
+- Formatos numéricos estão separados dos valores armazenados?
+- Edições estruturais podem quebrar referências?
+- Regras de validação são apenas UI ou validação real de negócio?
+- VBA precisa ser preservado?
+- A saída reabre com sucesso?
+- Estruturas críticas são verificadas após salvar?
+
+### Exercício
+
+Construa um workbook fictício de operações mensais:
+
+1. Crie um `.xlsx` com planilhas `Summary` e `Transactions`.
+2. Adicione uma linha de cabeçalho e pelo menos dez transações fictícias.
+3. Use valores Python `date` ou `datetime` explícitos para datas.
+4. Adicione uma fórmula do Excel à planilha de resumo.
+5. Explique por que seu teste deve verificar o texto da fórmula em vez de esperar que openpyxl a calcule.
+6. Formate células monetárias com number format.
+7. Reutilize um estilo de cabeçalho em vez de criar formatação desconectada por célula.
+8. Congele a linha de cabeçalho das transações.
+9. Crie uma tabela do Excel sobre os dados.
+10. Adicione uma validação por lista à coluna de status.
+11. Salve em um novo caminho.
+12. Reabra e verifique nomes de planilhas, texto da fórmula, nome da tabela, quantidade de validações e um estilo crítico.
+13. Adicione uma função read-only que calcule um total Python das linhas salvas.
+14. Faça falhas aparecerem com contexto útil.
+
+Desafios extras:
+
+- crie um gráfico a partir dos valores do resumo;
+- adicione um nome definido e inspecione-o após recarregar;
+- compare designs de export normal e write-only;
+- processe um `DataFrame` do pandas e use openpyxl somente para a camada de apresentação;
+- projete um teste seguro de round-trip `.xlsm` com `keep_vba=True` sem tentar editar o projeto VBA.
+
+### Conexões com conceitos anteriores
+
+`openpyxl` se apoia diretamente no conteúdo anterior:
+
+- **funções e módulos:** isole etapas de geração e validação;
+- **exceções:** reporte entradas malformadas ou incompatíveis;
+- **`pathlib`:** modele caminhos de origem e destino;
+- **datas:** armazene valores temporais Python com formatos de planilha;
+- **`decimal`:** decida explicitamente como valores monetários exatos atravessam a fronteira para células numéricas do Excel;
+- **`logging`:** registre caminhos, nomes de planilha, contagens e falhas sem esconder exceções;
+- **`os` e `shutil`:** descubra, prepare, copie e arquive workbooks com segurança;
+- **`pandas`:** transforme dados tabulares antes de openpyxl montar a apresentação final do Excel.
+
+### Referências primárias
+
+- [documentação do openpyxl](https://openpyxl.readthedocs.io/)
+- [tutorial do openpyxl](https://openpyxl.readthedocs.io/en/stable/tutorial.html)
+- [Optimised Modes](https://openpyxl.readthedocs.io/en/stable/optimized.html)
+- [Working with styles](https://openpyxl.readthedocs.io/en/stable/styles.html)
+- [Worksheet tables](https://openpyxl.readthedocs.io/en/stable/worksheet_tables.html)
+- [Data validation](https://openpyxl.readthedocs.io/en/stable/validation.html)
+- [Worksheet editing](https://openpyxl.readthedocs.io/en/stable/editing_worksheets.html)
+- [openpyxl no PyPI](https://pypi.org/project/openpyxl/)
+
+No momento em que este capítulo foi preparado, o PyPI listava openpyxl 3.1.5 como release estável mais recente. O currículo mira a série 3.1.x em vez de depender de uma versão futura sem limite.
+
+## 67. Próximo capítulo
+
+A Fase 9 agora possui duas camadas práticas de dados/workbook:
+
+```text
+pandas -> transform tabular data
+openpyxl -> construct and maintain Excel workbooks
+```
+
+A próxima biblioteca planejada é **`requests`**, quando a fronteira deixa arquivos locais e passa para serviços HTTP e APIs.
+
+Antes de avançar, pratique gerando workbooks que possam ser inspecionados manualmente e validados automaticamente. Automação de planilhas fica confiável quando tanto o contrato de dados quanto o contrato do workbook são explícitos.
diff --git a/external-libraries/02-openpyxl/examples/load_and_iterate.py b/external-libraries/02-openpyxl/examples/load_and_iterate.py
new file mode 100644
index 0000000..3644ec1
--- /dev/null
+++ b/external-libraries/02-openpyxl/examples/load_and_iterate.py
@@ -0,0 +1,24 @@
+from pathlib import Path
+from tempfile import TemporaryDirectory
+
+from openpyxl import Workbook, load_workbook
+
+
+with TemporaryDirectory() as temp_dir:
+ path = Path(temp_dir) / "orders.xlsx"
+
+ workbook = Workbook()
+ worksheet = workbook.active
+ worksheet.title = "Orders"
+ worksheet.append(["order_id", "amount"])
+ worksheet.append([101, 25.5])
+ worksheet.append([102, 40.0])
+ worksheet.append([103, 34.5])
+ workbook.save(path)
+
+ reloaded = load_workbook(path, read_only=True, data_only=True)
+ sheet = reloaded["Orders"]
+ rows = list(sheet.iter_rows(min_row=2, values_only=True))
+ print(f"orders: {len(rows)}")
+ print(f"total: {sum(amount for _, amount in rows):.2f}")
+ reloaded.close()
diff --git a/external-libraries/02-openpyxl/examples/styled_report.py b/external-libraries/02-openpyxl/examples/styled_report.py
new file mode 100644
index 0000000..368e2e5
--- /dev/null
+++ b/external-libraries/02-openpyxl/examples/styled_report.py
@@ -0,0 +1,34 @@
+from pathlib import Path
+from tempfile import TemporaryDirectory
+
+from openpyxl import Workbook, load_workbook
+from openpyxl.styles import Alignment, Font, PatternFill
+
+
+with TemporaryDirectory() as temp_dir:
+ path = Path(temp_dir) / "styled.xlsx"
+
+ workbook = Workbook()
+ worksheet = workbook.active
+ worksheet.title = "Report"
+ worksheet.append(["item", "amount"])
+ worksheet.append(["Service", 1250.5])
+
+ header_fill = PatternFill(fill_type="solid", fgColor="D9EAF7")
+ for cell in worksheet[1]:
+ cell.font = Font(bold=True)
+ cell.fill = header_fill
+ cell.alignment = Alignment(horizontal="center")
+
+ worksheet["B2"].number_format = '#,##0.00'
+ worksheet.freeze_panes = "A2"
+ worksheet.column_dimensions["A"].width = 18
+ worksheet.column_dimensions["B"].width = 14
+ workbook.save(path)
+
+ reloaded = load_workbook(path)
+ sheet = reloaded["Report"]
+ print(f"header bold: {sheet['A1'].font.bold}")
+ print(f"number format: {sheet['B2'].number_format}")
+ print(f"freeze panes: {sheet.freeze_panes}")
+ reloaded.close()
diff --git a/external-libraries/02-openpyxl/examples/table_and_validation.py b/external-libraries/02-openpyxl/examples/table_and_validation.py
new file mode 100644
index 0000000..e213935
--- /dev/null
+++ b/external-libraries/02-openpyxl/examples/table_and_validation.py
@@ -0,0 +1,42 @@
+from pathlib import Path
+from tempfile import TemporaryDirectory
+
+from openpyxl import Workbook, load_workbook
+from openpyxl.worksheet.datavalidation import DataValidation
+from openpyxl.worksheet.table import Table, TableStyleInfo
+
+
+with TemporaryDirectory() as temp_dir:
+ path = Path(temp_dir) / "catalog.xlsx"
+
+ workbook = Workbook()
+ worksheet = workbook.active
+ worksheet.title = "Catalog"
+ worksheet.append(["item", "status"])
+ worksheet.append(["Keyboard", "active"])
+ worksheet.append(["Mouse", "inactive"])
+
+ table = Table(displayName="CatalogTable", ref="A1:B3")
+ table.tableStyleInfo = TableStyleInfo(
+ name="TableStyleMedium2",
+ showFirstColumn=False,
+ showLastColumn=False,
+ showRowStripes=True,
+ showColumnStripes=False,
+ )
+ worksheet.add_table(table)
+
+ validation = DataValidation(
+ type="list",
+ formula1='"active,inactive"',
+ allow_blank=False,
+ )
+ worksheet.add_data_validation(validation)
+ validation.add("B2:B20")
+ workbook.save(path)
+
+ reloaded = load_workbook(path)
+ sheet = reloaded["Catalog"]
+ print(f"tables: {list(sheet.tables.keys())}")
+ print(f"validations: {len(sheet.data_validations.dataValidation)}")
+ reloaded.close()
diff --git a/external-libraries/02-openpyxl/examples/workbook_basics.py b/external-libraries/02-openpyxl/examples/workbook_basics.py
new file mode 100644
index 0000000..72a5cdb
--- /dev/null
+++ b/external-libraries/02-openpyxl/examples/workbook_basics.py
@@ -0,0 +1,23 @@
+from pathlib import Path
+from tempfile import TemporaryDirectory
+
+from openpyxl import Workbook, load_workbook
+
+
+with TemporaryDirectory() as temp_dir:
+ path = Path(temp_dir) / "report.xlsx"
+
+ workbook = Workbook()
+ worksheet = workbook.active
+ worksheet.title = "Summary"
+ worksheet.append(["product", "units", "unit_price", "total"])
+ worksheet.append(["Notebook", 2, 15.0, "=B2*C2"])
+ worksheet.append(["Marker", 5, 4.0, "=B3*C3"])
+ workbook.save(path)
+
+ reloaded = load_workbook(path, data_only=False)
+ sheet = reloaded["Summary"]
+ print(f"sheet: {sheet.title}")
+ print(f"rows: {sheet.max_row}")
+ print(f"formula: {sheet['D2'].value}")
+ reloaded.close()
diff --git a/external-libraries/02-openpyxl/examples/write_only_export.py b/external-libraries/02-openpyxl/examples/write_only_export.py
new file mode 100644
index 0000000..2e28eb8
--- /dev/null
+++ b/external-libraries/02-openpyxl/examples/write_only_export.py
@@ -0,0 +1,23 @@
+from pathlib import Path
+from tempfile import TemporaryDirectory
+
+from openpyxl import Workbook, load_workbook
+
+
+with TemporaryDirectory() as temp_dir:
+ path = Path(temp_dir) / "streamed.xlsx"
+
+ workbook = Workbook(write_only=True)
+ worksheet = workbook.create_sheet("Data")
+ worksheet.append(["id", "value"])
+ worksheet.append([1, 10])
+ worksheet.append([2, 20])
+ worksheet.append([3, 30])
+ workbook.save(path)
+
+ reloaded = load_workbook(path, read_only=True, data_only=True)
+ sheet = reloaded["Data"]
+ rows = list(sheet.iter_rows(min_row=2, values_only=True))
+ print(f"rows: {len(rows)}")
+ print(f"sum: {sum(value for _, value in rows)}")
+ reloaded.close()
diff --git a/external-libraries/README.es.md b/external-libraries/README.es.md
index 3e6cb30..737255d 100644
--- a/external-libraries/README.es.md
+++ b/external-libraries/README.es.md
@@ -10,7 +10,7 @@
La Fase 9 introduce paquetes de terceros después de completar las bases del lenguaje Python y de la biblioteca estándar.
-Las bibliotecas externas añaden una nueva responsabilidad de ingeniería: **contratos de dependencias**. Un programa pasa a depender no solo de Python, sino también de versiones de paquetes, estado de instalación, notas de versión y límites de compatibilidad.
+Las bibliotecas externas agregan una nueva responsabilidad de ingeniería: **contratos de dependencias**. Un programa pasa a depender no solo de Python, sino también de versiones de paquetes, estado de instalación, release notes y límites de compatibilidad.
## Estado
@@ -19,7 +19,7 @@ Las bibliotecas externas añaden una nueva responsabilidad de ingeniería: **con
## Ruta de aprendizaje
1. ✅ [`pandas`: Trabajando con Datos Tabulares](01-pandas/README.es.md)
-2. ⏳ `openpyxl`: automatización de libros de Excel
+2. ✅ [`openpyxl`: Automatizando Libros de Excel](02-openpyxl/README.es.md)
3. ⏳ `requests`: clientes HTTP y consumo de APIs
4. ⏳ `pytest`: pruebas automatizadas
@@ -27,10 +27,10 @@ Las bibliotecas externas añaden una nueva responsabilidad de ingeniería: **con
Los ejemplos ejecutables publicados en esta fase usan las dependencias declaradas en [`requirements-external.txt`](../requirements-external.txt). El CI del repositorio instala ese archivo antes de ejecutar los ejemplos aprobados de bibliotecas externas.
-El capítulo de pandas apunta a **pandas 3.0.x**. pandas 3.0 es compatible con Python 3.11+, mientras este repositorio continúa validando los ejemplos en Python 3.13.
+Los contratos actuales apuntan a **pandas 3.0.x** y **openpyxl 3.1.x**. pandas 3.0 soporta Python 3.11+, mientras PyPI declara Python 3.8+ para openpyxl 3.1.5. Este repositorio valida los ejemplos en Python 3.13.
-## Por qué esta fase llega ahora
+## Por qué esta fase viene ahora
Las fases anteriores establecieron colecciones, funciones, errores, archivos, módulos, CSV/JSON, fechas, rutas, logging, iteración, aritmética decimal y contratos de filesystem. Las bibliotecas externas deben construir sobre esas habilidades, no sustituirlas.
-El siguiente capítulo planificado es **`openpyxl`**.
+El próximo capítulo planificado es **`requests`**.
diff --git a/external-libraries/README.md b/external-libraries/README.md
index 4638655..90f4a2d 100644
--- a/external-libraries/README.md
+++ b/external-libraries/README.md
@@ -19,7 +19,7 @@ External libraries add a new engineering responsibility: **dependency contracts*
## Learning path
1. ✅ [`pandas`: Working with Tabular Data](01-pandas/README.md)
-2. ⏳ `openpyxl`: Excel workbook automation
+2. ✅ [`openpyxl`: Automating Excel Workbooks](02-openpyxl/README.md)
3. ⏳ `requests`: HTTP clients and API consumption
4. ⏳ `pytest`: automated testing
@@ -27,10 +27,10 @@ External libraries add a new engineering responsibility: **dependency contracts*
Published executable examples from this phase use the dependencies declared in [`requirements-external.txt`](../requirements-external.txt). Repository CI installs that file before executing approved external-library examples.
-The pandas chapter targets **pandas 3.0.x**. pandas 3.0 supports Python 3.11+, while this repository continues to validate examples on Python 3.13.
+The current contracts target **pandas 3.0.x** and **openpyxl 3.1.x**. pandas 3.0 supports Python 3.11+, while PyPI declares Python 3.8+ for openpyxl 3.1.5. This repository validates the examples on Python 3.13.
## Why this phase comes now
The earlier phases established collections, functions, errors, files, modules, CSV/JSON, dates, paths, logging, iteration, decimal arithmetic, and filesystem contracts. External libraries should build on those skills rather than replace them.
-The next planned chapter is **`openpyxl`**.
+The next planned chapter is **`requests`**.
diff --git a/external-libraries/README.pt-BR.md b/external-libraries/README.pt-BR.md
index 9e38175..64f0502 100644
--- a/external-libraries/README.pt-BR.md
+++ b/external-libraries/README.pt-BR.md
@@ -19,7 +19,7 @@ Bibliotecas externas acrescentam uma nova responsabilidade de engenharia: **cont
## Trilha de aprendizagem
1. ✅ [`pandas`: Trabalhando com Dados Tabulares](01-pandas/README.pt-BR.md)
-2. ⏳ `openpyxl`: automação de workbooks do Excel
+2. ✅ [`openpyxl`: Automatizando Workbooks do Excel](02-openpyxl/README.pt-BR.md)
3. ⏳ `requests`: clientes HTTP e consumo de APIs
4. ⏳ `pytest`: testes automatizados
@@ -27,10 +27,10 @@ Bibliotecas externas acrescentam uma nova responsabilidade de engenharia: **cont
Os exemplos executáveis publicados nesta fase usam as dependências declaradas em [`requirements-external.txt`](../requirements-external.txt). O CI do repositório instala esse arquivo antes de executar os exemplos aprovados de bibliotecas externas.
-O capítulo de pandas tem como alvo **pandas 3.0.x**. O pandas 3.0 suporta Python 3.11+, enquanto este repositório continua validando os exemplos em Python 3.13.
+Os contratos atuais têm como alvo **pandas 3.0.x** e **openpyxl 3.1.x**. O pandas 3.0 suporta Python 3.11+, enquanto o PyPI declara Python 3.8+ para openpyxl 3.1.5. Este repositório valida os exemplos em Python 3.13.
## Por que esta fase vem agora
As fases anteriores estabeleceram coleções, funções, erros, arquivos, módulos, CSV/JSON, datas, caminhos, logging, iteração, aritmética decimal e contratos de filesystem. Bibliotecas externas devem construir sobre essas habilidades, não substituí-las.
-O próximo capítulo planejado é **`openpyxl`**.
+O próximo capítulo planejado é **`requests`**.
diff --git a/requirements-external.txt b/requirements-external.txt
index be344ad..f3b6511 100644
--- a/requirements-external.txt
+++ b/requirements-external.txt
@@ -1,3 +1,4 @@
# External-library dependencies used by published executable examples.
# Keep version ranges aligned with the documented curriculum contracts.
pandas>=3.0,<3.1
+openpyxl>=3.1,<3.2
diff --git a/scripts/example_manifest.txt b/scripts/example_manifest.txt
index 9fa8568..2e536c3 100644
--- a/scripts/example_manifest.txt
+++ b/scripts/example_manifest.txt
@@ -163,3 +163,8 @@ external-libraries/01-pandas/examples/dataframe_basics.py
external-libraries/01-pandas/examples/filter_and_assign.py
external-libraries/01-pandas/examples/groupby_summary.py
external-libraries/01-pandas/examples/merge_tables.py
+external-libraries/02-openpyxl/examples/load_and_iterate.py
+external-libraries/02-openpyxl/examples/styled_report.py
+external-libraries/02-openpyxl/examples/table_and_validation.py
+external-libraries/02-openpyxl/examples/workbook_basics.py
+external-libraries/02-openpyxl/examples/write_only_export.py