Introducción
Nightjar Print Studio acepta inscripciones a talleres y prepara entradas descargables. Una inscripción puede esperar de forma segura mientras el worker está detenido, pero el actual reconoce la entrega demasiado pronto. Después, repetir trabajo terminado puede producir entradas duplicadas.
Necesitas manejo básico del terminal y poder leer una función pequeña de Python, su sangría y una consulta de diccionario. El breve puente siguiente repasa esos elementos e introduce SQL y transacciones. RabbitMQ, Python, la base SQLite y el sitio completo están preparados; solo editas handler.py. No necesitas instalación, cuenta externa, pago ni correo.
Usa Nightjar para las páginas reales de inscripción y entradas, y Workstation para los comandos. Cada unidad comienza de forma independiente. El sitio y las etiquetas exactas siguen en inglés. Es una práctica de interrupción de workers en una máquina, no una promesa de alta disponibilidad ni entrega exactamente una vez.
Sigue una inscripción hasta su entrada
En este paso, empieza en Nightjar y observa el funcionamiento normal del negocio antes de modificar el worker.
Abre Explore the evening, lee los detalles y sigue Plan your visit. Vuelve con The evening. En Guest name, escribe Normal guest y selecciona Register for the evening. La página nueva muestra un ID, como R-0002, y Your ticket is being prepared. Guardar la inscripción no equivale a emitir la entrada. Your registrations también contiene la entrada existente de Mina Patel; consérvala.

El formulario Guest name pertenece a la página del taller; el estado de la inscripción y la entrada tienen páginas separadas.
Cambia a Workstation. pwd muestra el directorio actual y cat muestra un archivo de texto. El directorio debe ser /home/labex/project/ticket-desk:
pwd
cat handler.py
Una cola guarda mensajes pendientes. RabbitMQ es el broker real; un worker recibe el mensaje con ID y nombre, escribe la entrada y reconoce la entrega. Un reconocimiento indica a RabbitMQ que puede retirar esa entrega; no crea por sí mismo una entrada.
En Python, def handle(...) define una función y sus líneas indentadas forman el cuerpo. El worker preparado la llama para cada mensaje. job es un diccionario: job['id'] y job['name'] consultan sus valores. ch.basic_ack(delivery.delivery_tag) reconoce esta entrega en el canal que la recibió. checkpoint es una pausa didáctica suministrada: normalmente no hace nada; una opción --pause coincidente espera Enter o Ctrl+C. No simula trabajo ni eventos del broker.
La base contiene tablas, colecciones de filas. db.execute(...) ejecuta SQL. INSERT INTO tickets... añade una entrada. Cada ? se rellena con un valor de la tupla siguiente, para tratar los nombres como datos y no como órdenes SQL. with db: confirma las escrituras al terminar normalmente su bloque indentado, o las revierte si sale una excepción. El commit hace persistentes los cambios. El código actual reconoce antes de trabajar; repararás ese orden después.
python3 worker.py ejecuta el worker preparado una vez: recibe un mensaje listo, llama al manejador y termina. Ejecútalo sin interrumpirlo:
python3 worker.py
Espera Received R-0002 redelivered=False y Handler returned.. Los ID pueden variar si añades inscripciones. redelivered viene de RabbitMQ, no de la base de entradas. Una ejecución normal crea una entrada real pese al orden inseguro.
Vuelve a Nightjar, abre Your registrations, selecciona Normal guest y Open ticket. Pulsa Download ticket y lee el archivo: debe contener el mismo ID y nombre. Sigue Back to registration. La entrada de Mina Patel también debe abrirse. Este es el recorrido que tu reparación debe conservar.
Si aparece No ready registrations, consulta la lista antes de reintentar: una ejecución anterior puede haber procesado el mensaje. Para corregir un nombre mal escrito, registra de nuevo el nombre requerido; cada ID sigue siendo independiente. No edites la base para forzar un resultado.
Reconoce solo el trabajo terminado
En este paso, empieza en Workstation. Moverás el reconocimiento después de la transacción e interrumpirás un trabajo incompleto para ver cómo RabbitMQ lo devuelve.
Si el worker reconoce primero y luego falla, RabbitMQ ya no tiene una entrega pendiente que reintentar. Reconocer después del commit mantiene la entrega sin reconocer hasta que existe la entrada. Esto protege frente a una interrupción del worker; no une RabbitMQ y SQLite en una transacción.
Reemplaza solo handler.py con el bloque siguiente. cat > handler.py sobrescribe el archivo; <<'PYTHON' inicia un here-document literal. Pega todo el bloque, incluido el último PYTHON en su propia línea. Conserva la sangría de cuatro espacios. La transacción no cambia; solo se mueve el reconocimiento:
cat > handler.py <<'PYTHON'
from common import checkpoint
def handle(ch, delivery, job, db, pause):
checkpoint(pause, 'before-write')
with db:
db.execute('INSERT INTO tickets(registration_id,name) VALUES (?,?)',
(job['id'], job['name']))
checkpoint(pause, 'after-commit')
ch.basic_ack(delivery.delivery_tag)
PYTHON
En Nightjar, abre The evening, registra Retry guest y deja abierta su página. Regresa a Workstation. La opción --pause before-write se detiene justo antes de cualquier escritura:
python3 worker.py --pause before-write
Espera Paused before-write. y pulsa Ctrl+C en el terminal. Esta interrupción intencional termina con estado 130. Muestra inmediatamente el estado del comando anterior mediante $?:
echo $?
Espera 130. En Nightjar, actualiza la inscripción Retry guest: todavía no tiene entrada. Vuelve a Workstation y ejecuta sin pausa:
python3 worker.py
Ahora espera redelivered=True. RabbitMQ devolvió realmente la entrega no reconocida al cerrarse la conexión anterior. Actualiza la inscripción en Nightjar, abre la entrada y descárgala. Sigue perteneciendo al mismo ID.
Si no aparece la pausa, revisa la opción y cat handler.py. Si pulsaste Enter por error, el worker terminó normalmente; registra otro Retry guest y repite la interrupción. Si reconociste con el código antiguo y después interrumpiste, python3 desk.py status muestra el ID y python3 desk.py retry R-0003 solicita otra entrega para esa inscripción; sustituye el ID real. El reintento es una recuperación explícita del alumno, nunca una acción de evaluación. No reintentes entradas terminadas hasta añadir la deduplicación en el siguiente paso.
Haz segura la repetición del resultado en la base
En este paso, empieza en Workstation. Queda otra ventana de fallo: SQLite puede confirmar la entrada y el worker detenerse antes de reconocer a RabbitMQ. El código actual crearía otra entrada al repetir la entrega.
Aquí, idempotencia significa que procesar otra vez el mismo ID deja un solo resultado de negocio. Los nombres no identifican inscripciones: dos personas llamadas Same name necesitan entradas separadas. La tabla preparada completed tiene una clave primaria id que impide dos filas con el mismo ID.
INSERT OR IGNORE intenta insertar ese ID. Su rowcount es 1 si la fila es nueva y 0 si ya existe. (job['id'],) es una tupla de un valor; la coma importa. if added: ejecuta la inserción indentada solo para un ID nuevo. Ambas escrituras deben permanecer en el mismo bloque with db:. De lo contrario, una caída podría guardar la clave sin la entrada y omitir permanentemente una inscripción válida.
Reemplaza la función por esta implementación completa y pequeña. El punto interior está deliberadamente entre las escrituras para observar la reversión:
cat > handler.py <<'PYTHON'
from common import checkpoint
def handle(ch, delivery, job, db, pause):
checkpoint(pause, 'before-write')
with db:
added = db.execute('INSERT OR IGNORE INTO completed VALUES (?)',
(job['id'],)).rowcount
checkpoint(pause, 'inside-transaction')
if added:
db.execute('INSERT INTO tickets(registration_id,name) VALUES (?,?)',
(job['id'], job['name']))
checkpoint(pause, 'after-commit')
ch.basic_ack(delivery.delivery_tag)
PYTHON
En Nightjar, registra Same name dos veces. Reciben ID distintos, normalmente R-0004 y R-0005. En Workstation, detén el primero dentro de la transacción:
python3 worker.py --pause inside-transaction
En Paused inside-transaction., pulsa Ctrl+C. Actualiza esa inscripción en Nightjar: no existe entrada y tampoco debe sobrevivir la clave de finalización. En Workstation, reintenta el mismo ID, esta vez con pausa después del commit:
python3 worker.py --pause after-commit
En Paused after-commit., abre su inscripción en Nightjar: ya existe una entrada. Vuelve a Workstation, pulsa Ctrl+C y procesa otra vez la entrega no reconocida:
python3 worker.py
Espera redelivered=True, pero al actualizar Nightjar debe seguir habiendo exactamente una entrada. La inserción duplicada devolvió 0, por lo que el manejador omitió una segunda entrada y aun así reconoció la entrega.
Regresa a Workstation. --drain repite la operación de un mensaje hasta que no queden mensajes listos:
python3 worker.py --drain
La segunda inscripción Same name recibe ahora su propia entrada. desk.py status lee las filas reales:
python3 desk.py status
Para probar una entrega duplicada explícita, usa el primer ID Same name mostrado. En la secuencia normal es R-0004; sustituye el tuyo si difiere:
python3 desk.py retry R-0004
python3 worker.py --drain
En Nightjar, ambos ID Same name tienen una entrada descargable y se conservan Mina Patel, Normal guest y Retry guest. No debe quedar ninguna inscripción pendiente. No ready registrations por sí solo no lo demuestra: los trabajos perdidos también pueden dejar vacía la cola.
Si Python informa de un error de sangría o sintaxis, vuelve a copiar la función y reintenta; el trabajo no reconocido vuelve a RabbitMQ. Mantén juntas ambas escrituras. No borres entradas ni claves para aparentar una solución correcta.
Si procesaste un ID terminado con el código anterior no idempotente, corrige primero handler.py. La acción preparada repair permite recuperarlo sin reiniciar a otros invitados. Busca el ID con python3 desk.py status. Por ejemplo, si Normal guest es R-0002, previsualiza:
python3 desk.py repair R-0002
Muestra la entrada original más antigua a conservar, los números extra a quitar y el registro de finalización de este ID solamente. Todavía no cambia nada. --apply aplica explícitamente el plan en una transacción:
python3 desk.py repair R-0002 --apply
Sustituye el ID afectado. La herramienta rechaza ID desconocidos, sin entrada o con nombres que no coinciden. Conserva el número y contenido originales más antiguos, las demás inscripciones y sus entradas. Marca el ID reparado como terminado para que el manejador corregido pueda omitirlo de forma segura. Nunca consume ni reconoce mensajes. Abre la entrada conservada en Nightjar y usa python3 worker.py --drain para procesar las entregas restantes con el código reparado. La reparación no permite que un manejador inseguro supere las pruebas aisladas.
La evaluación lee resultados reales y ejecuta tu manejador sin modificarlo contra otro broker RabbitMQ y bases desechables. Prueba interrupciones, reentrega real y reversión sin consumir, reconocer ni reparar trabajos activos. Son efectos de base seguros al repetirse, no una garantía de entrega exactamente una vez; el correo o los pagos externos necesitan su propio diseño.
Resumen
Recuperaste tareas de entradas en cola, distinguiste entrega de finalización del negocio y preservaste inscripciones distintas con nombres iguales. Reconocer después del commit permite reintentar; una clave de finalización y la entrada en una transacción hacen seguro el reintento para este resultado de base. No garantiza entrega exactamente una vez ni cubre correo, pagos, fallos del broker o todos los fallos de producción.


