<a id="ch-03"></a>

## Capítulo 3 — Programación I

<!-- open-cyber-academy:lesson-figures:start ch-03 -->
### Mapa de la lección

Lección 03 publicada. Mapa, ejemplo, EX005.

```mermaid
flowchart TD
  N0["Resultados de aprendizaje y conexión"]
  N1["3.1 Por qué programa un estudiante de seguridad"]
  N2["3.2 Las construcciones básicas"]
  N3["3.4 Actividad práctica"]
  N4["Síntesis del capítulo"]
  N0 --> N1 --> N2 --> N3 --> N4
  N4 --> EX["Ejemplo resuelto"]
  EX --> Q["Ejercicio EX005"]
```

### Dibujo lógico

```mermaid
flowchart LR
  N0["Resultados de aprendizaje y conexión"]
  N1["3.1 Por qué programa un estudiante de seguridad"]
  N2["3.2 Las construcciones básicas"]
  N3["3.4 Actividad práctica"]
  N4["Síntesis del capítulo"]
  N0 --> N1 --> N2 --> N3 --> N4 --> Q["EX005"]
```

```text
[Resultados de aprendizaje y conexión]
    |
    v
[3.1 Por qué programa un estudiante de seguridad]
    |
    v
[3.2 Las construcciones básicas]
    |
    v
[3.4 Actividad práctica]
    |
    v
[Síntesis del capítulo]
    |
    v
[Ejemplo resuelto] --> [Ejercicio EX005]
```
<!-- open-cyber-academy:lesson-figures:end ch-03 -->


### Resultados de aprendizaje y conexión

Después de este capítulo usted podrá (1) escribir programas breves en Python con variables, condiciones, bucles, funciones, listas y diccionarios; (2) leer texto estructurado (CSV) con la biblioteca estándar; (3) convertir una pregunta sobre un sistema («¿qué dispositivos han superado el fin de su soporte?») en un filtro, un recuento y un informe; y (4) probar un programa con un caso pequeño cuya respuesta ya conoce. Se apoya en el ciclo de vida de los dispositivos del capítulo 2 y prepara el capítulo 8, en el que los programas se vuelven robustos frente a entradas defectuosas.

### 3.1 Por qué programa un estudiante de seguridad

El trabajo de seguridad en los servicios críticos consiste sobre todo en saber qué se tiene y en advertir qué ha cambiado. Ambas cosas exigen procesar datos: inventarios con miles de filas, registros con millones de líneas, avisos publicados a diario. Una hoja de cálculo sirve para cincuenta filas; un programa breve sirve para cincuenta mil y puede volver a ejecutarse mañana. Aquí se usa Python porque su biblioteca estándar lee CSV, JSON, fechas y bases de datos sin instalar nada, y porque el código legible es más fácil de revisar, una propiedad que importa cuando su resultado informa una decisión sobre la seguridad de los pacientes.

### 3.2 Las construcciones básicas

Una **variable** da nombre a un valor (`year = 2026`). Una **condición** elige entre caminos (`if eos < year:`). Un **bucle** repite el trabajo para cada elemento (`for row in rows:`). Una **función** empaqueta un paso con un nombre y unas entradas para poder probarlo por separado. Una **lista** contiene elementos en orden; un **diccionario** asocia claves con valores (`row["os"]`). El módulo `csv` convierte cada línea de un archivo CSV en un diccionario cuyas claves son las del encabezado. La clase `collections.Counter` cuenta cuántas veces aparece cada valor. Con esto se puede responder a la mayoría de las preguntas sobre un inventario.

Dos disciplinas importan desde el principio. Primero, **pruebe con una respuesta conocida**: antes de ejecutar sobre el archivo real, ejecute sobre cinco filas que comprobó a mano. Segundo, **imprima lo que excluyó**, no solo lo que encontró: un filtro que descarta en silencio las filas con la fecha en blanco informará de «ningún dispositivo sin soporte» precisamente para los dispositivos que nadie documentó.

### 3.3 Ejemplo resuelto y Lab 2: clasificación del inventario del CVH (sintético)

**Este caso es sintético.** El equipo de ingeniería clínica del CVH exporta una muestra de ocho filas de su inventario de dispositivos. La pregunta: ¿qué dispositivos ejecutan software que ha superado el fin de su soporte, dónde están y hay algún dispositivo no clínico en la red clínica?

```python
# Lab 2 - triage a synthetic hospital device inventory with the standard library (offline)
import csv, io
from collections import Counter
DATA = """asset,type,os,os_end_of_support,network,clinical
INF-01,infusion pump,RTOS-x 2.1,2030,clinical-vlan,yes
INF-02,infusion pump,RTOS-x 1.4,2021,clinical-vlan,yes
IMG-01,imaging workstation,Windows 7,2020,clinical-vlan,yes
IMG-02,imaging workstation,Windows 10,2025,clinical-vlan,yes
LAB-01,lab analyser,Linux 3.10,2019,lab-vlan,yes
ADM-01,office pc,Windows 11,2031,corporate,no
ADM-02,office pc,Windows 10,2025,clinical-vlan,no
BMS-01,building controller,vendor fw 4,2027,bms-vlan,no
"""
YEAR = 2026
rows = list(csv.DictReader(io.StringIO(DATA)))
unsupported = [r for r in rows if int(r["os_end_of_support"]) < YEAR]
print("assets:", len(rows), "| unsupported:", len(unsupported))
for r in unsupported:
    print(f"  {r['asset']:7} {r['type']:20} {r['os']:12} EoS {r['os_end_of_support']} clinical={r['clinical']}")
print("by network:", dict(Counter(r["network"] for r in unsupported)))
misplaced = [r["asset"] for r in rows if r["clinical"] == "no" and r["network"] == "clinical-vlan"]
print("non-clinical devices on clinical VLAN:", misplaced)
```

Salida esperada:

```text
assets: 8 | unsupported: 5
  INF-02  infusion pump        RTOS-x 1.4   EoS 2021 clinical=yes
  IMG-01  imaging workstation  Windows 7    EoS 2020 clinical=yes
  IMG-02  imaging workstation  Windows 10   EoS 2025 clinical=yes
  LAB-01  lab analyser         Linux 3.10   EoS 2019 clinical=yes
  ADM-02  office pc            Windows 10   EoS 2025 clinical=no
by network: {'clinical-vlan': 4, 'lab-vlan': 1}
non-clinical devices on clinical VLAN: ['ADM-02']
```

Interpretación: cuatro dispositivos clínicos no tienen soporte, lo que es un problema de planificación de sustituciones con controles compensatorios (capítulo 2). ADM-02 es distinto: un PC de oficina sin soporte en la red clínica es el hallazgo más barato de corregir; actualícelo y muévalo. Las filas son datos didácticos: las etiquetas de los dispositivos son inventadas y, aunque algunos nombres de sistemas operativos resulten familiares, trate cada año de fin de soporte como una entrada de muestra y compruebe cualquier producto real con la página de ciclo de vida que publica su fabricante.

### 3.4 Actividad práctica

Amplíe el Lab 2: (1) añada una fila con `os_end_of_support` vacío y observe el error; (2) cambie el programa para que esas filas se notifiquen como «desconocido: necesita responsable» en lugar de provocar un fallo o ser omitidas; (3) escriba un `assert` que compruebe que el recuento de dispositivos sin soporte para las ocho filas originales es 5. El paso (2) es el importante. Una fila notificada como «desconocido: necesita responsable» pone nombre a una carencia; una fila omitida la oculta, y un fallo detiene el informe para todos. El `assert` del paso (3) es una prueba con respuesta conocida: si un cambio posterior en el filtro hace que imprima 4 o 6, la prueba falla antes de que nadie lea un informe erróneo.

### 3.5 Errores comunes y límites

- Comparar números almacenados como texto (`"2025" < "2026"` funciona por casualidad; `"9" < "10"` no).
- Omitir en silencio las filas mal formadas.
- Fijar en el código el año actual, de modo que el informe quede obsoleto; léalo del reloj, pero manténgalo fijo en las pruebas.
- Tratar una exportación del inventario como la verdad: solo es tan buena como la última persona que la actualizó (capítulo 22).

En el Lab 2, el segundo error habría ocultado VEN-01 en EX005; el tercero habría hecho que los dispositivos de 2025 volvieran a parecer con soporte si el año se hubiera quedado en 2025; y el cuarto se aplica a toda la exportación: ADM-02 aparece en la VLAN clínica solo porque alguien registró correctamente su red.

### Ejercicios — capítulo 3

**EX005 — Lo desconocido es un hallazgo.** *Objetivo:* hacer visibles los datos que faltan. *Entradas:* el Lab 2 más una novena fila `VEN-01,ventilator,,,clinical-vlan,yes`. *Pasos:* modifique el programa para que imprima una sección de desconocidos; explique por qué una fecha de soporte desconocida en un ventilador debe escalarse. *Evidencia esperada:* el cambio en el código, la salida y dos frases. *Rúbrica:* 3 = sin fallo, VEN-01 notificado como desconocido, razón de escalamiento vinculada a la consecuencia clínica; 2 = funciona, sin razonamiento; 1 = evita el fallo omitiendo la fila; 0 = nada.

**EX006 — Una función que se puede probar.** *Objetivo:* separar la lógica de los datos. *Entradas:* el Lab 2. *Pasos:* escriba `def is_unsupported(row, year) -> bool`, pruébela con tres filas hechas a mano (con soporte, sin soporte y el año límite igual a `year`) y enuncie su regla para el límite. *Evidencia esperada:* la función, las pruebas y la regla. *Rúbrica:* 3 = función correcta, las tres pruebas, regla del límite enunciada y justificada; 2 = no se discute el límite; 1 = solo la función; 0 = nada.

### Síntesis del capítulo

Los programas breves y legibles convierten los inventarios en decisiones. Filtre, cuente e informe, y notifique siempre lo que no pudo clasificar. Pruebe con casos que conoce, mantenga la lógica en funciones y recuerde que el programa solo es tan veraz como sus datos.

<!-- open-cyber-academy:chapter-navigation:start ch-03 -->
### Siga estudiando — capítulo 03 de 40

[← Capítulo 02](#ch-02) · [Índice de capítulos del curso](../SUBJECTS.md) · [Capítulo 04 →](#ch-04)

Continúe con la siguiente lección escrita.
<!-- open-cyber-academy:chapter-navigation:end -->

## Worked answers

<a id="answer-ex005"></a>
**EX005.** Cambie el filtro para tratar por separado `row["os_end_of_support"] == ""`: `unknown = [r for r in rows if not r["os_end_of_support"].strip()]`, notifíquelos bajo «DESCONOCIDO: necesita responsable» y calcule `unsupported` solo a partir de las filas con fecha. La salida añade `VEN-01 ventilator`. Un ventilador con estado de soporte desconocido es un equipo de soporte vital en la red clínica cuya exposición a parches nadie puede precisar; escálelo a ingeniería clínica para obtener la declaración de soporte del fabricante y revisar su ubicación.

<a id="answer-ex006"></a>
**EX006.** `def is_unsupported(row, year): return int(row["os_end_of_support"]) < year`. Pruebas: fin de soporte 2030 → False; 2020 → True; fin de soporte igual a `year` (2026) → False con esta regla. Regla enunciada: un dispositivo cuyo soporte termina *durante* el año en curso todavía tiene soporte ahora, pero debe señalarse en una lista aparte de «termina este año»; una regla alternativa defendible trata `<=` como sin soporte, para ser conservadora. Cualquiera de las dos es aceptable si se enuncia y se prueba.
