Tema 1 · Fundamentos y origen de los eventos
Programación Dirigida por Eventos · Grado en Ingeniería Informática · 2026/27
Qué significa que un programa esté dirigido por eventos: quién controla el siguiente paso, inversión de control, el vocabulario fuente / evento / receptor y el viaje de un suceso desde el mundo físico (polling, interrupciones, IRQ, ISR, driver, latencia) hasta tu código. Lo aplicamos reescribiendo agenda.kt como agendaeventos.kt y clasificando diez situaciones reales.
El paradigma dirigido por eventos
Flujo secuencial frente a flujo dirigido por eventos, inversión de control, el vocabulario fuente / evento / receptor y el viaje de un suceso desde el mundo físico hasta tu código: polling, interrupciones, IRQ, ISR, driver y latencia.
Teoría 1. ¿Quién controla el siguiente paso?
Toda la asignatura gira alrededor de una pregunta muy sencilla: ¿quién decide cuál es el siguiente paso del programa?
En los programas que habéis escrito hasta ahora la respuesta era obvia: el propio programa. Ejecuta la instrucción 1, luego la 2, luego la 3. Si lees el código de arriba abajo, sabes en qué orden va a pasar todo.
En un programa dirigido por eventos esa respuesta cambia: el entorno determina cuándo llega un suceso (un clic, un mensaje, un temporizador que vence, un sensor que se dispara) y la aplicación se limita a reaccionar.
| Programa secuencial | Programa dirigido por eventos | |
|---|---|---|
| Quién decide el orden | El programa: 1 → 2 → 3 | El entorno decide cuándo llega cada suceso |
| Qué hace el código | Sigue su guion de principio a fin | Reacciona a lo que va ocurriendo |
| Orden de ejecución | Fijo, se lee en el código | Depende de cuándo llegan los eventos |
La Programación Orientada / Dirigida por Eventos (POE) es un paradigma general: el software reacciona a sucesos. Al terminar la unidad deberías poder:
- Comprender que la POE no es "cosa de ventanas", sino una forma general de organizar software que reacciona.
- Distinguir flujo secuencial y flujo dirigido por eventos.
- Explicar la cadena estímulo externo → evento software → reacción.
- Reconocer el paradigma en GUI, móviles, servidores, IoT y firmware.
- El orden en que están escritas las funciones en el fichero
- El momento en que llegan los eventos
- El orden alfabético de los nombres de los manejadores
- La prioridad que el compilador asigna a cada función
El entorno decide cuándo llega cada suceso; la aplicación solo reacciona. Por eso el orden de ejecución depende de la llegada de los eventos, no del código fuente.
- Una app móvil que reacciona a los toques en la pantalla
- Un script que lee un CSV, calcula la media y termina
- Un servidor web que atiende peticiones según van llegando
- El firmware de un termostato que reacciona a la interrupción de un sensor
La app, el servidor y el firmware esperan sucesos que no controlan. El script del CSV decide él solo el orden de todo: es secuencial.
Teoría 2. POO organiza piezas; POE organiza reacciones
No confundas la POE con una alternativa a la POO. Responden a preguntas distintas:
POO
Organiza piezas: objetos, estado y métodos.
POE
Organiza reacciones: sucesos, fuentes, receptores y reacciones.
Lo que añade la POE
Qué ocurre, quién lo comunica y cuándo se ejecuta cada respuesta.
En esta asignatura escribiréis POE con POO (Kotlin): los receptores serán objetos, lambdas o clases que implementan una interfaz.
Inversión de control (IoC)
La idea central del paradigma tiene nombre propio: inversión de control.
- La aplicación no inicia cada llamada ni decide el orden completo.
- Es el framework, el sistema operativo, una biblioteca o el hardware quien invoca la reacción que tú registraste.
- Se resume en el principio de Hollywood: «No nos llames; nosotros te llamaremos».
// Flujo convencional: MI código llama a la biblioteca, y yo decido cuándo
val texto = readLine()
procesar(texto)
// Inversión de control: entrego una reacción y OTRO decide cuándo llamarla
boton.setOnClickListener {
procesar("clic") // lo invocará el framework cuando el usuario pulse
}Fíjate en la diferencia: en el primer bloque, procesar se ejecuta justo después de readLine(). En el segundo, al llegar a esa línea no se ejecuta nada: solo se registra el código. Se ejecutará más tarde, cero, una o cien veces, según lo que haga el usuario.
hollywoodEs el principio de Hollywood: tú registras la reacción y el entorno te llama cuando toca.
- El programa invierte el orden de sus instrucciones para ir más rápido
- La POE sustituye a la POO, que deja de ser necesaria
- El entorno (framework, SO, biblioteca o hardware) invoca la reacción que la aplicación registró
- La aplicación llama a la biblioteca cada vez que necesita un dato
En IoC la aplicación no decide cuándo se ejecuta su código: lo registra y es el entorno quien lo invoca. La última opción es justo el flujo convencional.
Ejercicio 3. Ejercicio: ¿hay inversión de control?
Mira estos cinco fragmentos (Kotlin/Android y Arduino) y decide en cuáles es otro quien decide cuándo se ejecuta tu código.
// A
val n = readLine()!!.toInt()
println(n * 2)
// B
boton.setOnClickListener { contador++ }
// C
Handler(Looper.getMainLooper()).postDelayed({ mostrarAviso() }, 5000)
// D
val total = listOf(3, 4, 5).sum()// E (Arduino / ESP32)
attachInterrupt(digitalPinToInterrupt(PIN_BOTON), alPulsar, FALLING);Pista para razonar cada uno: pregúntate quién llama a la función y quién decide el momento.
Ver razonamiento
A: tu código llama a readLine() y se queda esperando; cuando vuelve, sigue en el orden que tú escribiste. Flujo convencional.
B: registras una lambda; el framework la invoca cuando el usuario pulsa. IoC (botón).
C: entregas mostrarAviso(); el sistema la activa al vencer el tiempo. IoC (temporizador).
D: llamas a sum() y obtienes el resultado al momento. Convencional.
E: registras alPulsar; el hardware, a través de una interrupción, decide cuándo se ejecuta. IoC (sensor / pin).
- A
- B
- C
- D
- E
B (botón), C (temporizador) y E (interrupción de un pin) registran una reacción que invoca otro. A y D son llamadas normales: tu código llama y recibe la respuesta en ese mismo punto.
mostrarAviso()?- La línea siguiente del código, que la llama explícitamente
- El sistema, al vencer el plazo de 5000 ms
- El usuario, al pulsar un botón
- Nadie: se ejecuta en cuanto se lee esa línea
La aplicación configura la reacción; el sistema la activa cuando vence el tiempo. Es uno de los cuatro casos clásicos de IoC: botón, temporizador, mensaje de red y sensor.
Teoría 4. Vocabulario esencial: evento, fuente, receptor…
Para hablar con precisión de eventos necesitamos cinco palabras. Apréndelas bien: aparecerán en todas las unidades.
| Término | Definición |
|---|---|
| Evento | Suceso relevante que el software puede comunicar y procesar. |
| Fuente / emisor | Componente, dispositivo o sistema que origina o publica el evento. |
| Receptor | Componente interesado en recibir la notificación. |
| Notificación | Comunicación que entrega el evento al receptor. |
| Reacción | Trabajo que realiza el receptor al procesar el evento. |
Todo evento lleva información
Un evento no es solo "algo ha pasado". Lleva cuatro datos:
| Campo | Pregunta | Ejemplos |
|---|---|---|
| Tipo | ¿Qué ocurrió? | clic, timeout, mensaje, temperatura alta |
| Origen | ¿Quién lo produjo? | botón, sensor, conexión, tarea |
| Datos | ¿Con qué información? | tecla, valor, coordenadas, contenido |
| Instante | ¿Cuándo? | marca de tiempo; ayuda a razonar sobre orden y latencia |
En código podría representarse así:
data class Evento(
val tipo: String, // "clic", "timeout", "temperatura_alta"
val origen: String, // "botonEnviar", "sensorNevera"
val datos: Any?, // tecla, valor leído, coordenadas…
val instante: Long // milisegundos desde el arranque, por ejemplo
)- Tipo
- Receptor
- Origen
- Datos
- Prioridad
- Instante
Tipo (qué), origen (quién lo produjo), datos (con qué información) e instante (cuándo). El receptor no forma parte del evento: es quien lo procesa.
receptorEs el receptor: recibe la notificación y ejecuta la reacción.
Ejercicio 5. Ejercicio: pon nombre a cada pieza
Escenario: estás en un editor de texto con el cuadro de búsqueda abierto. Pulsas la tecla Esc y el cuadro se cierra.
Desmonta la escena con el vocabulario del paso anterior antes de mirar la solución:
| Pieza | Tu respuesta |
|---|---|
| Fuente | ¿? |
| Evento (tipo, origen, datos, instante) | ¿? |
| Notificación | ¿? |
| Receptor | ¿? |
| Reacción | ¿? |
Ver una solución
Fuente: el usuario a través del teclado (y el sistema que capta la pulsación).
Evento: tipo «tecla pulsada»; origen: el teclado; datos: el código de la tecla Esc; instante: la marca de tiempo de la pulsación.
Notificación: el sistema / framework entrega ese evento al componente que tiene el foco.
Receptor: el manejador de teclado del cuadro de búsqueda.
Reacción: cerrar el cuadro de búsqueda.
- La pulsación de la tecla
- El teclado
- Entregar el evento al cuadro de búsqueda
- Cerrar el cuadro de búsqueda
La reacción es el trabajo del receptor: cerrar el cuadro. La pulsación es el evento, el teclado forma parte de la fuente y entregar el evento es la notificación.
- El instante
- Los datos (el código de la tecla)
- El origen
- El receptor
Ambos eventos tienen el mismo tipo (tecla pulsada) y el mismo origen (teclado). Lo que cambia son los datos: el código de la tecla.
instanteEl instante: cuándo ocurrió o se registró el suceso.
Teoría 6. Del mundo físico al software: polling o interrupción
Un clic no aparece por arte de magia en tu onClick. Recorre cinco etapas:
La etapa 2 merece atención: hay dos formas de detectar que algo ha ocurrido.
| Polling | Interrupción | |
|---|---|---|
| Idea | Consulta repetida: «¿ha ocurrido algo?» | Una señal pide atención a la CPU cuando ocurre el suceso |
| Ventaja | Muy simple | La CPU puede hacer otro trabajo hasta recibirla |
| Inconveniente | Consume CPU y la respuesta depende de la frecuencia de consulta | Más compleja de configurar |
// Polling: el bucle pregunta una y otra vez
void loop() {
if (digitalRead(PIN_BOTON) == LOW) { // ¿ha pasado algo?
encenderLed();
}
delay(100); // vuelve a preguntar dentro de 100 ms
}Estímulo → detección (polling o interrupción) → interpretación por driver y SO → evento software con tipo, origen y datos → notificación y reacción de la aplicación.
- Necesita un driver especial que no existe en microcontroladores
- Consume CPU y su tiempo de respuesta depende de la frecuencia de consulta
- Solo funciona con eventos de red
- La CPU no puede hacer nada más hasta que llega la interrupción
Preguntar sin parar gasta CPU; preguntar poco retrasa la detección. La última opción describe justo lo contrario de una interrupción, que libera a la CPU.
Teoría 7. IRQ, ISR y driver; latencia
Tres siglas que en el examen no se pueden mezclar:
IRQ
Interrupt Request: la solicitud o señal de interrupción que genera un dispositivo o controlador.
ISR
Interrupt Service Routine: el código que ejecuta la CPU para atender la interrupción.
Driver / controlador
Software que traduce los detalles del dispositivo a operaciones y datos utilizables por el sistema.
Latencia: el tiempo hasta poder reaccionar
Latencia = tiempo desde que ocurre el estímulo hasta que la aplicación puede reaccionar.
No se genera en un único sitio: se acumula a lo largo de todo el recorrido.
La capacidad de respuesta depende de todo el recorrido, no solo del código final. Optimizar tu onClick no sirve de nada si el retraso está en un polling lento.
- La señal eléctrica que un dispositivo envía para pedir atención
- El código que ejecuta la CPU para atender una interrupción
- El software que traduce los detalles de un dispositivo para el sistema operativo
- El tiempo que pasa desde el estímulo hasta la reacción
ISR = Interrupt Service Routine, la rutina (código) que atiende la interrupción. La primera opción es la IRQ, la tercera el driver y la cuarta la latencia.
- En la detección (polling o interrupción)
- En el driver
- En la entrega del evento a la aplicación
- Únicamente en el código final de la aplicación
- En la propia reacción
La latencia se acumula en detección, driver, transferencia, generación, entrega y reacción. Por eso la opción «únicamente en el código final» es falsa.
Ejercicio 8. Ejercicio: ¿dónde se pierde el tiempo?
Un ESP32 lee un pulsador con el loop() del paso 6: comprueba el pin y luego hace delay(100).
void loop() {
if (digitalRead(PIN_BOTON) == LOW) encenderLed();
delay(100);
}Razona estas situaciones:
- El usuario pulsa justo después de que el programa haya comprobado el pin. ¿Cuánto puede tardar, como mucho, en detectarse la pulsación (despreciando el tiempo de
digitalRead)? - Si en vez de
delay(100)pusiéramosdelay(10), ¿qué ganamos y qué perdemos? - Si registramos el pin con
attachInterrupt(...), ¿qué pasa con esa espera?
Ver razonamiento
1. Hasta la siguiente consulta: casi 100 ms de latencia de detección en el peor caso.
2. Detectamos antes (≈10 ms en el peor caso), pero la CPU pasa mucho más tiempo preguntando: más consumo para nada la mayoría de las veces.
3. La señal de interrupción (IRQ) avisa en cuanto ocurre el cambio y la CPU ejecuta la ISR: la latencia de detección deja de depender de la frecuencia de consulta, y la CPU puede hacer otro trabajo mientras tanto.
100Si el suceso ocurre justo después de mirar, no se ve hasta la siguiente consulta: casi 100 ms de latencia de detección.
IRQEs la IRQ (Interrupt Request). El código que la atiende es la ISR.
Teoría 9. Ampliación: la POO como soporte de la POE
La POO no sustituye al modelo de eventos: lo hace extensible y desacoplado. Tres mecanismos lo consiguen:
| Mecanismo | Qué aporta a la POE |
|---|---|
| Interfaz | Expresa el contrato de una reacción sin fijar la implementación. Una fuente puede notificar a receptores distintos mediante ese contrato común. |
| Polimorfismo | Distintos receptores comparten contrato y reaccionan de forma diferente a la misma notificación. |
| Composición | Objetos que colaboran para construir fuentes, receptores y cadenas de reacciones, sin concentrar toda la lógica en una clase. |
// Interfaz: el contrato de la reacción
interface ReceptorTemperatura {
fun onTemperatura(grados: Double)
}
// Polimorfismo: mismo contrato, reacciones distintas
class Pantalla : ReceptorTemperatura {
override fun onTemperatura(grados: Double) = println("Ahora: $grados °C")
}
class Alarma(private val umbral: Double) : ReceptorTemperatura {
override fun onTemperatura(grados: Double) {
if (grados > umbral) println("¡ALERTA! $grados °C")
}
}
// Composición: la fuente guarda una lista de receptores
class Sensor {
private val receptores = mutableListOf<ReceptorTemperatura>()
fun registrar(r: ReceptorTemperatura) { receptores.add(r) }
fun nuevaLectura(grados: Double) = receptores.forEach { it.onTemperatura(grados) }
}
fun main() {
val sensor = Sensor()
sensor.registrar(Pantalla())
sensor.registrar(Alarma(25.0))
sensor.nuevaLectura(30.0)
}El Sensor no sabe si al otro lado hay una pantalla, una alarma o un registro en fichero: solo conoce la interfaz. Añadir un receptor nuevo no obliga a tocar la fuente.
- Obliga a que todas las clases hereden de la fuente
- Define un contrato común; la fuente notifica a través de él sin conocer la clase concreta
- Hace que la fuente ejecute todas las reacciones dentro de su propio código
- Impide que haya más de un receptor registrado a la vez
La fuente solo conoce ReceptorTemperatura. Cualquier clase que implemente ese contrato puede registrarse, y cada una reacciona a su manera (polimorfismo).
main(), ¿cuántas líneas se imprimen?2Se notifica a los dos receptores: Pantalla imprime «Ahora: 30.0 °C» y Alarma(25.0) imprime la alerta porque 30.0 > 25.0. Total: 2 líneas.
Teoría 10. Ampliación: de Engelbart a los servidores
Hitos para entender la interacción moderna
Douglas Engelbart
Pionero de la interacción persona-ordenador; asociado al ratón y a la interacción directa.
Xerox PARC
Centro de investigación que impulsó las interfaces gráficas y la interacción moderna.
WIMP
Windows · Icons · Menus · Pointer: ventanas, iconos, menús y puntero.
Con una GUI el usuario puede hacer clic en cualquier sitio en cualquier momento: el programa ya no puede seguir un guion fijo. Por eso las interfaces gráficas popularizaron la POE… pero no la definen.
| Ámbito | Sucesos típicos |
|---|---|
| GUI y móviles | Clics, teclas, toques, ciclo de vida, sensores |
| Servidores | Conexiones, peticiones, mensajes, temporizadores |
| IoT y firmware | Sensores, actuadores, GPIO, interrupciones, temporizadores |
Inversión de control en cuatro situaciones
| Situación | Quién decide cuándo se reacciona |
|---|---|
| Botón | El usuario decide el clic; el framework invoca la reacción registrada. |
| Temporizador | La aplicación configura una reacción; el sistema la activa al vencer el tiempo. |
| Mensaje de red | La llegada del mensaje determina cuándo se ejecuta su procesamiento. |
| Sensor | El cambio medido determina cuándo se comunica un evento a sus consumidores. |
pointerWIMP = Windows, Icons, Menus, Pointer (puntero).
- La POE solo existe en programas con interfaz gráfica
- La GUI popularizó la POE, pero el paradigma también está en servidores, IoT y firmware
- Los servidores no son dirigidos por eventos porque no tienen usuario delante
- Xerox PARC inventó la inversión de control para los servidores web
La GUI es una aplicación muy importante del paradigma, pero la POE se generaliza a cualquier sistema que reaccione a sucesos sin orden fijo: servidores, IoT y firmware.
- La llegada del mensaje
- El orden en que se declararon las funciones
- Un bucle que el programador recorre a mano llamando a cada función
- El usuario, pulsando un botón
En el caso del mensaje de red, es su llegada la que dispara el procesamiento: otro ejemplo de inversión de control.
Repaso 11. Repaso: mapa mental y autoevaluación
| Memoriza | Distingue | Explica |
|---|---|---|
| POO organiza objetos; POE organiza reacciones. Evento = tipo + origen + datos + instante. | Fuente produce/detecta · receptor procesa · notificación conecta. | Polling vs. interrupción · IRQ vs. ISR · función del driver · significado de latencia. |
Ampliación: interfaces, polimorfismo y composición son las herramientas de POO para implementar POE. Engelbart, Xerox PARC y WIMP explican el origen de las GUI modernas; la POE se generaliza a servidores, IoT y firmware.
- Nada, es correcto
- Esa función es el driver
- Esa función es la ISR; la IRQ es la señal o solicitud de interrupción
- Esa función es el polling
La IRQ es la petición (la señal). La ISR es la rutina, el código que se ejecuta para atenderla.
- La CPU puede hacer otro trabajo hasta recibir la señal y la detección no depende de una frecuencia de consulta
- No necesita ningún tipo de hardware
- Elimina toda la latencia del sistema
- Permite que el programa decida el orden de los eventos
La interrupción libera a la CPU y avisa cuando ocurre el suceso. Pero no elimina toda la latencia: driver, entrega y reacción siguen sumando tiempo.
- Un botón cuyo listener invoca el framework al pulsarlo
- Un temporizador que activa una reacción configurada al vencer
- Un sensor cuyo cambio dispara la notificación a sus consumidores
- Una función que calcula el máximo de una lista y devuelve el resultado
Botón, temporizador y sensor (y mensaje de red) son los cuatro casos clásicos de IoC. Calcular un máximo es una llamada convencional.
latenciaEs la latencia, y depende de todo el recorrido, no solo del código final.
De agenda.kt a agendaeventos.kt
Un menú de consola que lo decide todo, reescrito en cuatro piezas: evento, fuente, receptor y un bucle «tonto» que solo pide y entrega. Y dónde está ese bucle en Android.
Teoría 1. Un vocabulario, tres contextos
Esta sesión práctica aplica el mismo vocabulario en tres sitios distintos:
| Bloque | Qué se hace | Tiempo |
|---|---|---|
| pde-plantilla | Vistas clásicas de Android: un botón, un interruptor y sus listeners. ¿Quién decide el flujo y dónde están las reacciones? | 45 min |
| pde-consola · agenda.kt | Un menú de consola secuencial. El programa lo decide todo: pregunta, espera, elige, repite. | 20 min |
| agendaeventos.kt | El mismo programa reescrito en piezas separadas: fuente, evento y receptor. | 35 min |
| Debate | Diez situaciones de la vida real (lección siguiente). | opcional |
El vocabulario de hoy
Fuente
Quién origina el suceso: una persona, un sensor, un temporizador, otro programa. En consola: el teclado y la función que lo lee.
Evento
El suceso concreto, con sus datos: «clic en Enviar», «timeout a los 5 s», «mensaje de Ana». En código: un valor de un tipo que lo representa.
Receptor
El código que escucha y reacciona: un listener, una lambda, una función manejar(). Sabe qué hacer, no cuándo le toca.
En pde-plantilla la respuesta ya se intuye: escribís cosas como boton.setOnClickListener { ... } y nunca llamáis vosotros a esa lambda. Alguien la llama. En consola vamos a construir ese "alguien" a mano para ver qué hay dentro.
- Cuándo le toca ejecutarse, pero no qué hacer
- Qué hacer, pero no cuándo le toca
- Qué hacer y cuándo, porque él controla el bucle
- Nada: solo reenvía el evento a la fuente
El receptor contiene la reacción (qué hacer); cuándo se ejecuta lo decide quien le entrega el evento.
- La lista de tareas
- El
whenque elige la rama - El teclado y la función que lo lee
- La función
println
En consola, los sucesos llegan del teclado, y la función que lo lee (con readLine()) es la que los origina en el programa.
Teoría 2. agenda.kt: el programa que lo decide todo
Este es el punto de partida, en versión plegada (el contenido de las ramas viene después):
fun main() {
val tareas = mutableListOf<String>()
var salir = false
while (!salir) { // 1. el bucle que espera
println("\n--- Agenda de tareas ---")
println("1. Añadir tarea")
println("2. Listar tareas")
println("3. Eliminar tarea")
println("4. Salir")
print("Elige una opción: ")
when (readLine()?.trim()) { // 2. punto de bloqueo 3. el bucle decide
"1" -> { /* añadir tarea */ }
"2" -> { /* listar tareas */ }
"3" -> { /* eliminar tarea */ }
"4" -> { salir = true; println("Hasta luego.") } // 4. la única salida
else -> println("Opción no reconocida.")
}
}
}- El bucle que espera. Mientras nadie elija «Salir», el programa vuelve a preguntar. Nadie le avisa de nada: es él quien pregunta, una y otra vez.
- El punto de bloqueo.
readLine()detiene el programa hasta que el usuario pulsa Intro. Es la única fuente de sucesos, y está enterrada dentro del bucle. - El bucle decide. El
whencompara un texto y elige la rama. La lógica de qué hacer vive dentro del propio bucle. - La única salida. Poner
salir = truees lo único que rompe el ciclo: el programa termina cuando él mismo lo decide.
Ahora las ramas desplegadas:
"1" -> {
print("Descripción de la tarea: ")
val desc = readLine() ?: "" // ¡vuelve a leer del teclado dentro de la reacción!
tareas.add(desc)
println("Tarea añadida.")
}
"2" -> {
if (tareas.isEmpty()) println("No hay tareas.")
else tareas.forEachIndexed { i, t -> println("$i: $t") }
}
"3" -> {
print("Índice a eliminar: ")
val idx = readLine()?.toIntOrNull()
if (idx != null && idx in tareas.indices) {
tareas.removeAt(idx)
println("Tarea eliminada.")
} else {
println("Índice no válido.")
}
}readLine()readLine() detiene el programa hasta recibir una línea. Es la única fuente de sucesos y está metida dentro del bucle.
- Las reacciones vuelven a leer del teclado: fuente y receptor mezclados
- No compila porque falta el
elsedelwhen - Para añadir una opción hay que modificar el bucle principal
- El estado se modifica y se muestra desde el mismo sitio
El when sí tiene else. Los problemas son de diseño: fuente y receptor mezclados, estado/reacción/pantalla juntos y un bucle que hay que tocar ante cualquier cambio.
Ejercicio 3. Ejercicio: agenda.kt en ejecución
Esto es lo que se ve en la consola de pde-consola tras dos vueltas del bucle:
--- Agenda de tareas ---
1. Añadir tarea
2. Listar tareas
3. Eliminar tarea
4. Salir
Elige una opción: 1
Descripción de la tarea: Estudiar eventos
Tarea añadida.
--- Agenda de tareas ---
1. Añadir tarea
2. Listar tareas
3. Eliminar tarea
4. Salir
Elige una opción: 2
0: Estudiar eventos
--- Agenda de tareas ---
...
Elige una opción: ▌Tres observaciones:
- Lo que teclea el usuario es la única información que entra en el programa, y solo se lee cuando el programa lo pide, nunca antes.
- El menú se repinta en cada vuelta: es el programa quien lleva la iniciativa.
- El cursor parpadea: programa parado. Está bloqueado en
readLine(). No hace nada más hasta que pulses Intro: ni cuenta el tiempo, ni escucha la red, ni lee un sensor.
Ver respuesta
No. Mientras está bloqueado en readLine() el programa solo puede recibir una línea de teclado: el teclado es su única fuente y está enterrada en el bucle. Para enterarse de otros sucesos haría falta separar las fuentes del bucle, que este no se quede bloqueado esperando solo al teclado y que cada suceso llegue como un evento a un receptor. Es justo el camino que empieza agendaeventos.kt y que Android ya resuelve con su propio bucle.
readLine(), llega un mensaje de red. ¿Qué ocurre?- El programa interrumpe la lectura y muestra el mensaje
- Nada: el programa no se entera, solo puede recibir lo que se teclea
- El
whenañade una rama nueva automáticamente - El sistema operativo llama a la rama
else
El teclado es la única fuente y el programa está bloqueado esperándola. Ni temporizadores ni red ni sensores pueden llegar a él.
1: Llamar a AnaforEachIndexed numera desde 0: se imprime «0: Comprar pan» y «1: Llamar a Ana».
Teoría 4. agendaeventos.kt (1/4): el evento y el estado
Reescribimos el mismo programa separando cuatro piezas. El bucle deja de decidir y pasa a entregar:
Y alrededor, el bucle de eventos en main(), que solo pide un evento y lo entrega.
Antes de leer nada del teclado, definimos qué puede pasar:
// --- Evento: qué puede pasar, con los datos que necesite cada caso ---
sealed class EventoAgenda {
data class AnadirTarea(val descripcion: String) : EventoAgenda()
object ListarTareas : EventoAgenda()
data class EliminarTarea(val indice: Int) : EventoAgenda()
object Salir : EventoAgenda()
object OpcionInvalida : EventoAgenda()
}
// --- Estado: lo único que persiste entre eventos ---
class EstadoAgenda {
val tareas = mutableListOf<String>()
var enMarcha = true
}- Un tipo cerrado de sucesos: con
sealed classsolo existen estos cinco eventos. El compilador obliga al receptor a tratarlos todos en elwhen. - El evento lleva sus datos:
AnadirTareatransporta la descripción;EliminarTarea, el índice. Ya no hay que volver a preguntar dentro de la reacción. - El estado, fuera del bucle: lo que cambia entre evento y evento. En Android será
remember { mutableStateOf(...) }o un campo de la Activity.
Fíjate en que ListarTareas o Salir son object: no necesitan datos, basta con una única instancia. AnadirTarea y EliminarTarea son data class porque cada evento lleva información distinta.
EventoAgenda como sealed class?- Que los eventos se ejecutan más rápido
- Que solo existen los eventos declarados y el compilador obliga a tratarlos todos en el
when - Que no hace falta escribir un receptor
- Que los eventos se guardan automáticamente en disco
Un tipo cerrado: no puede aparecer un sexto evento por sorpresa y, si el receptor olvida uno, no compila.
EventoAgenda transportan datos?AnadirTareaListarTareasEliminarTareaSalirOpcionInvalida
AnadirTarea lleva la descripción y EliminarTarea el índice. Los demás son object sin datos.
Teoría 5. agendaeventos.kt (2/4 y 3/4): fuente y receptor
La fuente: leerEvento()
// --- Fuente: traduce entrada bruta de teclado en un evento con significado ---
fun leerEvento(): EventoAgenda {
println("\n--- Agenda de tareas ---")
println("1. Añadir tarea")
println("2. Listar tareas")
println("3. Eliminar tarea")
println("4. Salir")
print("Elige una opción: ")
return when (readLine()?.trim()) {
"1" -> {
print("Descripción de la tarea: ")
EventoAgenda.AnadirTarea(readLine() ?: "")
}
"2" -> EventoAgenda.ListarTareas
"3" -> {
print("Índice a eliminar: ")
EventoAgenda.EliminarTarea(readLine()?.toIntOrNull() ?: -1)
}
"4" -> EventoAgenda.Salir
else -> EventoAgenda.OpcionInvalida
}
}- Sigue leyendo del teclado, pero es el único sitio del programa donde ocurre: todo
readLine()vive aquí. - Traduce, no ejecuta: pide los datos que el evento necesita y lo construye. No añade, no borra, no lista.
- Hasta el error es un evento: una opción desconocida se devuelve como
OpcionInvaliday ya decidirá el receptor.
El receptor: manejar()
// --- Receptor: un manejador por tipo de evento ---
fun manejar(evento: EventoAgenda, estado: EstadoAgenda) {
when (evento) {
is EventoAgenda.AnadirTarea -> {
estado.tareas.add(evento.descripcion)
println("Tarea añadida.")
}
is EventoAgenda.ListarTareas -> {
if (estado.tareas.isEmpty()) println("No hay tareas.")
else estado.tareas.forEachIndexed { i, t -> println("$i: $t") }
}
is EventoAgenda.EliminarTarea -> {
if (evento.indice in estado.tareas.indices) {
estado.tareas.removeAt(evento.indice)
println("Tarea eliminada.")
} else {
println("Índice no válido.")
}
}
is EventoAgenda.Salir -> {
estado.enMarcha = false
println("Hasta luego.")
}
is EventoAgenda.OpcionInvalida -> println("Opción no reconocida.")
}
}- Un
whensobre el tipo de evento: cinco eventos, cinco reacciones. Si olvidas uno, no compila. - Solo toca el estado e imprime. No lee del teclado: los datos ya vienen dentro del evento (
evento.indiceya está listo). - Salir también es una reacción: no hay
breaknireturnescondido; cambiaenMarchay el bucle terminará solo.
readLine()?- En
leerEvento()y enmanejar() - Solo en
main() - Solo en
leerEvento() - En las tres funciones
La fuente es el único sitio que toca el teclado. El receptor recibe los datos ya empaquetados dentro del evento.
Salir que termine el programa?- Llamando a
exitProcess(0)dentro demanejar() - Con un
breakescondido en la fuente - Cambiando
estado.enMarchaafalse, de modo que el bucle termina solo - Lanzando una excepción que captura
main()
Salir es una reacción más: modifica el estado (enMarcha = false) y la condición del while hace el resto.
Teoría 6. agendaeventos.kt (4/4): el bucle «tonto»
// --- Bucle de eventos: ya no decide QUÉ hacer,
// solo obtiene un evento y lo entrega ---
fun main() {
val estado = EstadoAgenda()
while (estado.enMarcha) {
val evento = leerEvento() // pide un evento
manejar(evento, estado) // lo entrega
}
}Cuatro líneas que no saben nada de tareas:
- Sigue habiendo un
while. Estamos en consola: nadie nos avisa, así que alguien tiene que preguntar. Pero el bucle ya no contiene ni un solo"1","2"o"3". - Pide un evento. La fuente se encarga de esperar y traducir.
- Lo entrega. El receptor se encarga de reaccionar.
main()no sabe qué significa ninguno de los eventos.
Hemos pasado de un bucle que decide a un bucle que entrega. Añadir una opción nueva ya no toca esta parte.
¿Y en pde-plantilla, dónde está este while?
No está en vuestro código. Vive dentro de Android: es el bucle de mensajes del hilo principal. Vosotros solo escribís los receptores.
| Consola (agendaeventos.kt) | Android (pde-plantilla) |
|---|---|
while (estado.enMarcha) { ... } escrito a mano |
Bucle de mensajes del hilo principal, dentro de Android |
leerEvento() |
El sistema detecta toques y teclas y genera los eventos |
manejar(evento, estado) |
setOnClickListener { ... }: vuestro manejar() |
EstadoAgenda |
Campos de la Activity o remember { mutableStateOf(...) } |
while de main()?- En el método
onCreateque escribe el alumno - Dentro de Android: el bucle de mensajes del hilo principal
- No existe: en Android no hay ningún bucle
- En cada
setOnClickListener, que lleva su propiowhile
El bucle sigue existiendo, pero lo escribe el framework: es el bucle de mensajes del hilo principal. Vosotros solo aportáis los receptores.
manejar() para un botón?setOnClickListenersetOnClickListener { … } registra el receptor del clic: es vuestro manejar().
Ejercicio 7. Ejercicio: ¿a qué pieza pertenece?
Para cada trozo de código, decide a qué pieza de agendaeventos.kt pertenece: fuente, evento, receptor, estado o bucle.
| # | Fragmento |
|---|---|
| 1 | EventoAgenda.EliminarTarea(readLine()?.toIntOrNull() ?: -1) |
| 2 | data class AnadirTarea(val descripcion: String) : EventoAgenda() |
| 3 | estado.tareas.removeAt(evento.indice) |
| 4 | var enMarcha = true |
| 5 | manejar(leerEvento(), estado) dentro de while (estado.enMarcha) |
| 6 | else -> EventoAgenda.OpcionInvalida |
Ver solución
1 → fuente (lee el teclado y construye el evento). 2 → evento (definición del tipo con sus datos). 3 → receptor (reacción que cambia el estado). 4 → estado (persiste entre eventos). 5 → bucle (pide y entrega). 6 → fuente (traduce una entrada desconocida en un evento; no la trata).
EventoAgenda.EliminarTarea(readLine()?.toIntOrNull() ?: -1)?fuenteLee del teclado y construye el evento con su dato: es trabajo de la fuente.
estado.tareas.removeAt(evento.indice)?- Fuente
- Evento
- Bucle
- Receptor
Usa el dato que trae el evento para cambiar el estado: es una reacción, parte del receptor manejar().
else -> EventoAgenda.OpcionInvalida, ¿en qué pieza está y por qué?- En la fuente: traduce una entrada desconocida en un evento y deja que el receptor decida
- En el receptor: imprime «Opción no reconocida.»
- En el bucle: es la condición de salida
- En el estado: guarda el último error
Hasta el error es un evento. La fuente no lo trata: lo devuelve como OpcionInvalida y el receptor imprime el mensaje.
Ejercicio 8. Ejercicio: añadir «Vaciar agenda» y predecir
Queremos una opción nueva, «5. Vaciar agenda», que borre todas las tareas.
En agenda.kt habría que meter una rama "5" -> { ... } en el when del bucle principal: el corazón del programa.
En agendaeventos.kt los cambios quedan repartidos así:
// Evento: un caso más
object VaciarAgenda : EventoAgenda()
// Fuente: una opción más en el menú y en la traducción
println("5. Vaciar agenda")
"5" -> EventoAgenda.VaciarAgenda
// Receptor: una reacción más
is EventoAgenda.VaciarAgenda -> {
estado.tareas.clear()
println("Agenda vaciada.")
}
// Bucle main(): ... ¡nada!En un programa dirigido por eventos basta con añadir un evento y su receptor (y, en consola, enseñar a la fuente a producirlo). El bucle no se entera.
Predice la salida
Con la agenda vacía, el usuario teclea 3 y, cuando le piden el índice, escribe abc.
Sigue el evento: readLine()?.toIntOrNull() devuelve null → el operador ?: lo cambia por -1 → la fuente devuelve EliminarTarea(-1) → el receptor comprueba si -1 está en estado.tareas.indices…
- La
sealed class EventoAgenda - La fuente
leerEvento() - El receptor
manejar() - El bucle de
main()
Evento nuevo, traducción nueva en la fuente y reacción nueva en el receptor. main() no cambia: solo pide y entrega.
3 seguido de abc?Índice no válido.-1 no está en los índices de la lista, así que el receptor imprime «Índice no válido.»
main() llama a leerEvento() → La fuente imprime el menú y se bloquea en readLine() → La fuente traduce «2» en EventoAgenda.ListarTareas → main() entrega el evento a manejar() → El receptor imprime la lista (o «No hay tareas.»)El bucle pide → la fuente espera y traduce → el bucle entrega → el receptor reacciona.
Repaso 9. Repaso: de un bucle que decide a un bucle que entrega
| agenda.kt | agendaeventos.kt | |
|---|---|---|
| «Qué ha pasado» | Un String comparado a mano |
Un valor con nombre y datos |
| Dónde se lee el teclado | En el bucle y en las reacciones | Solo en la fuente |
| Añadir una opción | Tocar el bucle | Evento + traducción + reacción |
| Quién decide qué hacer | El bucle | El receptor |
- El bucle de
main() - La fuente
leerEvento() - El receptor
manejar() - La clase
EstadoAgenda
El bucle solo pide y entrega; la fuente solo traduce. La decisión de qué hacer vive en el receptor.
EstadoAgenda?- El bucle de mensajes del hilo principal
- Un campo de la Activity o
remember { mutableStateOf(...) } - El método
setOnClickListener - El fichero
AndroidManifest.xml
El estado es lo que persiste entre eventos: en Android, un campo de la Activity o un estado de Compose con remember { mutableStateOf(...) }.
while?- Porque Kotlin no permite programas sin bucles
- Porque en consola nadie nos avisa: alguien tiene que preguntar por el siguiente suceso
- Porque la
sealed classlo exige - Por error: debería eliminarse
En consola no hay framework que entregue eventos, así que el bucle lo escribimos nosotros. En Android lo escribe el sistema.
Debate: ¿fuente, evento o receptor?
Diez situaciones de la vida real (clics, timeouts, mensajes, sensores y cambios de datos) para practicar la clasificación fuente / evento / receptor y descubrir quién escribe el bucle.
Ejercicio 1. Las reglas del debate
Diez situaciones cotidianas, agrupadas en cinco familias: clic, timeout, mensaje recibido, sensor y cambio de datos. Para cada una hay que rellenar tres columnas:
| Fuente | Evento | Receptor |
|---|---|---|
| ¿Quién origina el suceso? | ¿Qué ocurre exactamente, y con qué datos? | ¿Qué código está escuchando y reacciona? |
Intenta resolver cada tarjeta antes de girarla:
En los pasos siguientes repasaremos las diez situaciones con las respuestas del profesor, de dos en dos o de cuatro en cuatro, y nos fijaremos en las trampas típicas: confundir la fuente con el canal, o el evento con el dispositivo que lo produce.
- Nada, es correcto
- Fuente y evento son iguales: el evento debe ser lo que ocurre (el clic) con sus datos
- El receptor debería ser el usuario
- Falta indicar el sistema operativo como evento
Si dos columnas coinciden, algo falla. El evento es el clic en Enviar, con el elemento pulsado y los datos del formulario.
- Clic
- Compilación
- Timeout
- Sensor
- Cambio de datos
- Herencia
Las cinco familias son clic, timeout, mensaje recibido, sensor y cambio de datos.
Ejercicio 2. Situaciones 1-4: clics y timeouts
| # | Situación | Fuente | Evento | Receptor |
|---|---|---|---|---|
| 01 | El usuario pulsa «Enviar» en un formulario web. | El usuario, a través del ratón o la pantalla táctil, y el navegador que capta el clic. | Clic en el botón «Enviar», con el elemento pulsado y los datos del formulario. | El manejador onSubmit / addEventListener('click', …) que valida y envía la petición. |
| 02 | Doble clic sobre un icono del escritorio. | El usuario + el controlador del ratón / sistema operativo, que detecta el patrón de doble clic. | «Doble clic sobre el icono X». | El manejador del escritorio que abre la aplicación asociada. |
| 03 | Una petición HTTP no recibe respuesta en 5 segundos. | El temporizador (timer) del cliente HTTP. | Vencimiento del plazo de espera a los 5000 ms. | El callback onTimeout / bloque catch que cancela la petición y muestra un error. |
| 04 | Una sesión se cierra sola tras 15 minutos de inactividad. | El temporizador de inactividad del servidor o de la aplicación. | Expiración de la sesión. | El manejador que invalida la sesión y redirige al login. |
Qué aprender de estas cuatro:
- En los clics la fuente no es solo "el usuario": hay una pieza que capta el suceso (navegador, controlador del ratón, SO). En el doble clic, además, alguien tiene que reconocer el patrón de dos clics seguidos.
- En los timeouts no hay ninguna persona: la fuente es un temporizador. El evento es justamente que no ha pasado nada durante un plazo.
- El servidor que no responde
- El usuario que hizo la petición
- El temporizador del cliente HTTP
- El bloque
catch
La fuente es el timer del cliente HTTP: es quien origina el suceso «ha vencido el plazo». El catch / onTimeout es el receptor.
- El navegador que capta el clic
- El manejador
onSubmit/addEventListener('click', …)que valida y envía - Los datos del formulario
- El ratón
El receptor es el código que escucha y reacciona: el manejador que valida y envía la petición.
expiraciónEl evento es la expiración de la sesión; la fuente, el temporizador de inactividad; el receptor, el manejador que la invalida y redirige al login.
Ejercicio 3. Situaciones 5-6: mensajes recibidos
| # | Situación | Fuente | Evento | Receptor |
|---|---|---|---|---|
| 05 | Llega un mensaje nuevo de WhatsApp al móvil. | El servidor de mensajería, que empuja la notificación al dispositivo. | «Mensaje entrante», con remitente y contenido. | El servicio de notificaciones de la app, que muestra el aviso. |
| 06 | Un microservicio recibe en una cola (Kafka / RabbitMQ) el aviso de que un pedido se ha pagado. | El servicio de pagos, que publica el mensaje en la cola. | Mensaje «PedidoPagado» con el identificador del pedido. | El consumidor (listener) suscrito a la cola, que actualiza el estado del pedido. |
Qué aprender de estas dos:
- En la 05, desde el punto de vista del móvil, la fuente inmediata es el servidor de mensajería que empuja la notificación. Tu amigo escribió el mensaje, pero el suceso que llega al dispositivo lo origina el servidor.
- En la 06 aparece una trampa clásica: la cola no es la fuente. Kafka o RabbitMQ son el canal por el que viaja la notificación. Quien origina el suceso es el servicio de pagos, que publica; quien reacciona es el consumidor suscrito.
- La cola de Kafka / RabbitMQ
- El consumidor suscrito a la cola
- El servicio de pagos, que publica el mensaje
- El mensaje «PedidoPagado»
La fuente es el servicio de pagos. La cola es el canal, «PedidoPagado» es el evento y el consumidor es el receptor.
el identificador del pedidoEl mensaje «PedidoPagado» lleva el identificador del pedido, para que el consumidor sepa qué pedido actualizar.
- Remitente
- Contenido
- Nivel de batería del móvil
- Orientación de la pantalla
El evento «mensaje entrante» lleva remitente y contenido.
Ejercicio 4. Situaciones 7-8: sensores
| # | Situación | Fuente | Evento | Receptor |
|---|---|---|---|---|
| 07 | El acelerómetro del móvil detecta que has girado la pantalla. | El sensor de orientación (hardware) y su controlador. | Cambio de orientación: de vertical a horizontal. | El OrientationEventListener / el sistema de diseño que recoloca la interfaz. |
| 08 | El sensor de temperatura de una nevera inteligente detecta que sube por encima del umbral. | El sensor de temperatura del electrodoméstico. | «Temperatura > umbral», con el valor leído. | El manejador que dispara una alerta al móvil del usuario. |
Qué aprender de estas dos:
- Aquí la fuente es hardware. Conecta con la lección 1: estímulo físico → detección (polling o interrupción) → driver/controlador → evento software → reacción. En la 07 la respuesta menciona explícitamente "el sensor y su controlador".
- El evento no es "el sensor", sino lo que ha cambiado y con qué datos: la nueva orientación, o el valor de temperatura que ha superado el umbral.
- En la 08 el evento no es cada lectura, sino un suceso con significado: cruzar el umbral. Alguien ha convertido muchas medidas en un único evento relevante.
- El acelerómetro
- El cambio de orientación de vertical a horizontal
- El
OrientationEventListener - La interfaz recolocada
El acelerómetro (y su controlador) es la fuente; el OrientationEventListener o el sistema de diseño, el receptor; la interfaz recolocada, la reacción. El evento es el cambio de orientación.
Fuente (sensor) → evento con su dato → receptor (manejador) → reacción (alerta al móvil).
Ejercicio 5. Situaciones 9-10: cambios de datos
| # | Situación | Fuente | Evento | Receptor |
|---|---|---|---|---|
| 09 | Alguien edita una celda de una hoja de cálculo colaborativa y los demás lo ven al momento. | El usuario que edita + el servidor de sincronización que propaga el cambio. | «Celda A1 modificada», con el nuevo valor. | El listener de cada cliente conectado, que repinta esa celda. |
| 10 | Añades un producto al carrito y el icono del carrito cambia de número. | El propio código de la app: el clic en «Añadir al carrito» modificó una variable de estado. | Cambio del estado carrito (nueva lista / tamaño). | La parte de la interfaz que observa ese estado (icono con contador) y se recompone o se repinta. |
Qué aprender de estas dos:
- En la 09 un único evento tiene muchos receptores: el listener de cada cliente conectado. La fuente es doble: quien edita y el servidor que propaga.
- La 10 es la más sutil: la fuente es tu propio código. El clic provocó un cambio de estado, y ese cambio de estado es a su vez un evento que escucha otra parte de la interfaz. Un evento produce otro.
- El servidor de la tienda
- El propio código de la app, que modificó una variable de estado tras el clic
- El icono del carrito
- El sistema operativo del móvil
La fuente es el propio código: el clic modificó el estado carrito y ese cambio es el evento que observa el icono con el contador.
- Ninguno: el servidor repinta todas las pantallas
- Solo uno: el usuario que editó
- Uno por cada cliente conectado: el listener de cada uno repinta la celda
- Exactamente dos: el servidor y el editor
Cada cliente conectado tiene su listener, que repinta la celda. Un evento, muchos receptores.
Teoría 6. ¿Quién escribe el bucle?
Tras pasar por pde-plantilla, pde-consola y la vida real, aparece siempre lo mismo:
pde-plantilla
El bucle existe, pero vive dentro de Android. Vosotros escribís los receptores (los listeners)… y actualizarInterfaz() a mano.
pde-consola
El bucle lo habéis escrito vosotros con un while. Primero decidía todo; después solo pide un evento y lo entrega.
Vida real
No hay bucle de código: el mundo (personas, sensores, redes, temporizadores) genera eventos sin parar. Alguien tiene que escuchar.
Misma estructura en los tres: fuente → evento → receptor. Solo cambia dónde vive el bucle que busca el siguiente suceso y quién repinta la pantalla.
¿Quién escribió el receptor?
Repasa las situaciones con esta pregunta:
| Quién lo escribe | Ejemplos del debate |
|---|---|
| Vosotros (la aplicación) | El onSubmit del formulario (01), el consumidor de «PedidoPagado» (06), el manejador que alerta de la nevera (08) |
| El framework | El sistema de diseño que recoloca la interfaz al girar (07, si no registras listener propio) |
| El sistema operativo | El manejador del escritorio que abre la aplicación (02) |
- pde-plantilla (Android)
- pde-consola (agendaeventos.kt)
- La vida real
- En ninguno
En consola el while es vuestro. En Android vive dentro del framework y en la vida real no hay bucle de código: el mundo genera eventos.
escribeEl bucle siempre existe. La pregunta es quién lo escribe: vosotros (consola), el framework (Android) o nadie en código (el mundo real, donde alguien tiene que escuchar).
Repaso 7. Repaso del debate
| Familia | Fuente típica |
|---|---|
| Clic | Usuario + dispositivo / navegador / SO |
| Timeout | Temporizador |
| Mensaje recibido | Servidor o servicio que publica |
| Sensor | Hardware + controlador |
| Cambio de datos | Quien edita / el propio estado de la app |
- El usuario
- Un temporizador
- El receptor
- La cola de mensajes
En las situaciones 3 y 4 la fuente es un temporizador (del cliente HTTP o de inactividad).
- Fuente: la alarma · Evento: la alarma · Receptor: el móvil
- Fuente: el temporizador del sistema · Evento: «hora programada alcanzada (7:00)» · Receptor: el manejador de la app que muestra la pantalla de alarma
- Fuente: el usuario · Evento: el botón posponer · Receptor: el temporizador
- Fuente: la pantalla · Evento: las 7:00 · Receptor: el usuario
Es un caso de timeout: la fuente es un temporizador, el evento es el vencimiento con su dato (la hora) y el receptor es el código que reacciona.
- Es la fuente del evento
- Es el receptor que actualiza el pedido
- Es el canal por el que viaja la notificación entre fuente y receptor
- Es el propio evento
La cola transporta el mensaje. Fuente: servicio de pagos; evento: «PedidoPagado»; receptor: el consumidor suscrito.