Cuando le preguntas a cualquier operador de televisión en vivo qué le gustaría ver en una herramienta de programación, casi invariablemente dicen: "Permítame construir un canal de la misma manera que organizo los archivos en una carpeta. Arrastre un programa, suéltelo donde lo desee, listo.
El problema es que una programación de canal no es una carpeta. Es un documento legal con restricciones estrictas. Dos programas no pueden reproducirse al mismo tiempo. El codificador no puede cambiar las entradas más rápido de cada cinco segundos. Un programa que ya ha comenzado no puede ser editado. Y una película que se extiende más allá de la medianoche debe tratarse como un solo programa, no dividirse en el límite del día.
Cada acción de arrastrar y soltar es una posible violación de restricciones esperando a ocurrir. Esta es la historia de ingeniería de cómo el programador de mStudio convierte el arrastrar y soltar, fácil de usar para el operador, en una línea de tiempo de canal legalmente garantizada en AWS MediaLive —incluyendo el momento en que un operador suelta un programa directamente sobre otro— y por qué el sistema resuelve esos conflictos automáticamente en lugar de entregar al operador un error y un rompecabezas para resolver.
Resumen rápido
| Aspecto | Detalle |
|---|---|
| Dominio | Programación de arrastrar y soltar para canales FAST en vivo en AWS MediaLive |
| Resolución de conflictos | Desplazamiento automático mediante un algoritmo de ajuste de 4 reglas |
| Brecha mínima entre vecinos | 6 segundos (mínimo de 5s de MediaLive + 1s de seguridad por deriva del reloj) |
| Manejo en cascada | Tiempos originales memorizados al primer toque, de modo que los desplazamientos encadenados producen una llamada de limpieza por programa |
| Seguridad de fecha pasada | Doble fase de protección — rechazo de entrada, más reversión silenciosa post-ajuste |
| Seguridad de reinicio diario | Búfer de 2 minutos para reproducción en vivo, más preservación de arrastre |
| Estado | En producción |
El problema de negocio: un calendario que no es un calendario
La programación de canales se parece a un software de calendario. Los operadores esperan que se comporte como tal: arrastrar una película a un espacio de las 9 PM, deslizar programas por la línea de tiempo, añadir una serie por lotes y ver cómo los episodios se organizan consecutivamente. Pero una programación de canal en vivo tiene restricciones que un calendario simplemente no posee:
- Los programas no pueden superponerse. La televisión en vivo reproduce exactamente una cosa a la vez.
- El codificador tiene un espaciado mínimo de acción. AWS MediaLive no cambiará las entradas más rápido de cada 5 segundos. Programe dos programas con 4 segundos de diferencia y el despliegue será rechazado.
- Los programas pasados no pueden ser editados. El tiempo de emisión ya pasó; los bits ya están en las pantallas de los espectadores.
- Los programas que cruzan la medianoche son una unidad. Una película que se reproduce de 23:30 a 01:15 debe ser manejada como un solo programa, no como dos medios programas divididos en el límite del día.
Una "casi superposición" de dos segundos no es un error del operador; es el resultado natural de arrastrar dos programas que casi, pero no del todo, encajan. El verdadero desafío de ingeniería es traducir "arrastrar una película a las 9 PM" en "una programación de canal legal". Si se hace mal, el operador se encuentra con un muro de errores en cada soltar, o, peor aún, descubre en el momento de la emisión que el codificador rechazó silenciosamente parte de la programación.
Qué significa "legal" en AWS MediaLive
Para que una programación sea legal en MediaLive, los programas adyacentes deben ser:
- Consecutivos — sin espacio entre ellos, o
- Separados por al menos 5 segundos — el espaciado mínimo de acción del codificador.
La trampa es todo lo que hay en medio. Una brecha de 1 segundo, una brecha de 3 segundos, una brecha de 4.9 segundos — todas parecen perfectamente bien en una UI, y todas son rechazadas en el momento del despliegue. Peor aún, el rechazo no es un fallo limpio y atómico; puede resultar en un canal parcialmente desplegado, donde algunas acciones de programación se implementaron en MediaLive y otras no.
Añade un margen de seguridad de 1 segundo para la deriva del reloj entre los servidores de aplicación y AWS, y el límite práctico se convierte en 6 segundos, no 5. Este número único — MIN_NEIGHBOUR_GAP_MS = 6000 — es la única constante en la que se basa todo el sistema de resolución de conflictos.
Por qué fallan los enfoques obvios
Antes de optar por la resolución automática, se consideraron y rechazaron varias estrategias más obvias:
"Rechazar cualquier desacuerdo y solicitar que el operador lo resuelva." Debido a esto, los operadores deben realizar manualmente cálculos de ajuste en cada soltar. Un arrastre de cinco segundos se convierte en un rompecabezas de cinco minutos, y el rompecabezas se vuelve más difícil a medida que la lista de reproducción crece. En la práctica, los operadores abandonan completamente el arrastrar y soltar y recurren a las hojas de cálculo.
"Ajustar todo a límites de 5 minutos para que nunca haya superposiciones." Esto resuelve el problema técnico destruyendo la intención del operador. Un programa destinado a comenzar a las 21:03:15 no debería saltar silenciosamente a las 21:05:00. La programación pertenece al operador, no a una función de redondeo.
"Detectar conflictos en el momento del despliegue en lugar de en el momento de soltar." Esto se siente más rápido en la UI, pero traslada el fallo al peor momento posible. Para cuando el operador hace clic en Deploy y ve "programación rechazada en el programa 47", ya ha pasado mentalmente de la edición que lo causó.
"Permitir micro-brechas en la UI y dejar que MediaLive las rechace." Esto devuelve los errores opacos del codificador directamente al operador, y puede dejar el canal en un estado medio desplegado del que es realmente difícil recuperarse.
La palanca que realmente funcionó fue resolver los conflictos automáticamente, en el momento de soltar, utilizando reglas deterministas, y reflejar la línea de tiempo corregida al operador de inmediato.
La solución: un algoritmo de ajuste de cuatro reglas
En cada acción de soltar, un algoritmo de ajuste examina cada par de programas adyacentes en el canal afectado —pares de nuevo-a-nuevo, nuevo-a-existente, o existente-a-existente cuya brecha cambió debido a la acción de soltar— y aplica exactamente una de cuatro reglas basadas en la brecha entre ellos:
- Brecha = 0 → ninguna acción. Consecutivo es legal y es casi seguro lo que el operador pretendía.
- Brecha ≥ 6 segundos → ninguna acción. El operador dejó espacio deliberadamente, probablemente para una pizarra o un bloque de anuncios.
- 0 < brecha < 6 segundos → desplazar el segundo programa hacia atrás para cerrar la brecha a cero.
- Brecha negativa (superposición) → desplazar el segundo programa hacia adelante por la cantidad de superposición.
Crucialmente, el algoritmo procesa pares adyacentes en una sola pasada hacia adelante sobre la línea de tiempo ordenada. Cada programa desplazado se convierte inmediatamente en el elemento "anterior" para la siguiente comparación, de modo que una acción de soltar que desencadena una reacción en cadena de desplazamientos se resuelve en un único recorrido lineal, sin necesidad de recursión.
Diagrama 1 · El Árbol de Decisión de Ajuste

Arquitectura del sistema
El programador se basa en un conjunto pequeño y enfocado de componentes:
- El backend de NestJS (
schedule.service.ts) gestiona el recorrido de ajuste, la memorización en cascada y el contrato de escritura con MediaLive. - Consulta de superposición de rangos de tiempo. Cuando llega una acción de soltar, el backend extrae todos los programas existentes cuyo rango de tiempo toca el rango del nuevo lote, más un búfer de 6 segundos en cada lado. Debido a que esta consulta opera en rangos de tiempo en lugar de fechas de calendario, los programas que cruzan la medianoche se manejan de manera idéntica a cualquier otro programa — no existe ninguna lógica especial de límites de fecha en el sistema.
- Línea de tiempo fusionada. Los nuevos DTOs de programa y los programas existentes consultados se fusionan en una única lista ordenada. El recorrido de ajuste se ejecuta contra esta línea de tiempo combinada.
- Mapa de
shiftedExistings. Para cualquier programa existente afectado por un desplazamiento, este mapa captura sus horas de inicio y fin originales la primera vez que se toca, y nunca las sobrescribe en toques posteriores. Esta estructura de datos es lo que hace que las cascadas de múltiples pasos sean seguras. - Función Lambda
DELETE_PROGRAM. Para cualquier programa desplazado que ya se haya desplegado en MediaLive, sus tiempos originales se envían a una función Lambda para su limpieza antes de que se actualice MongoDB. MIN_NEIGHBOUR_GAP_MS = 6000— la única constante a la que hacen referencia todas las reglas, todas las consultas de superposición y todos los búferes de seguridad.
Decisiones clave de ingeniería
1. Cuatro reglas, un recorrido, sin casos especiales. Las mismas cuatro reglas cubren cada escenario que un operador puede crear: un nuevo programa soltado entre dos existentes, dos nuevos programas que entran en conflicto entre sí, o un programa existente que es empujado a superposición por un desplazamiento anterior en la misma acción de soltar. No hay una ruta de código separada para ninguno de estos; cada caso se reduce a "examinar la brecha entre programas adyacentes y aplicar la regla".
2. Seis segundos, no cinco. MediaLive impone un espaciado mínimo de 5 segundos entre acciones de programación; programar dos cambios de entrada con 4.9 segundos de diferencia provoca un rechazo de despliegue. El sistema impone 6 segundos, un margen de seguridad de un segundo para la deriva del reloj entre el reloj del backend y el de AWS. Enviar una acción exactamente a los 5.000 segundos, cuando el reloj del codificador la lee como 4.997 segundos, produce rechazos intermitentes que parecen fallos de red y se sienten como errores irreproducibles. El segundo adicional convierte un modo de fallo intermitente en uno que simplemente nunca se activa.
Esto viene con un compromiso deliberado: un mínimo de 6 segundos significa que las pequeñas brechas consecutivas de 3 o 4 segundos se cierran a cero en lugar de preservarse. Este compromiso fue aceptado intencionalmente: las transiciones consecutivas son limpias en MediaLive, y una brecha visible de 3 segundos tiende a parecer un fallo para los espectadores, independientemente.
3. La memorización del tiempo original hace que las cascadas sean seguras. Una sola acción de soltar puede desencadenar una cadena de desplazamientos: el programa A desplaza a B, B desplaza a C, C desplaza a D. La llamada de limpieza a MediaLive debe apuntar al tiempo original de despliegue de cada programa, no a su tiempo desplazado en cascada; usar el tiempo incorrecto hace que MediaLive responda con "no se encontró ninguna acción", fallando silenciosamente la limpieza. El recorrido mantiene un mapa de programId → {oldStartTime, oldEndTime}, capturado la primera vez que se toca cada programa. Los desplazamientos en cascada posteriores solo actualizan la línea de tiempo en memoria; los originales memorizados permanecen intactos, y la limpieza siempre utiliza exactamente lo que MediaLive tiene registrado.
Diagrama 2 · Un ejemplo de cascada

4. Protecciones de fecha pasada, aplicadas en dos fases separadas. Las ediciones con fecha pasada se bloquean dos veces, deliberadamente:
- Fase 0, antes de que se ejecute el recorrido de ajuste: cualquier programa nuevo con una hora de inicio anterior a "ahora" rechaza directamente todo el lote, con un error claro. El recorrido de ajuste ni siquiera se ejecuta contra una entrada imposible.
- Fase 4, después del recorrido de ajuste: se manejan de manera diferente dos subcasos distintos. Un programa nuevo que el ajuste accidentalmente arrastró al pasado (raro, pero posible en los límites de reloj del tiempo de solicitud) se revierte silenciosamente a su tiempo original, previo al ajuste — la intención del operador se conserva, y el ajuste simplemente no se aplica. Un programa existente que un desplazamiento empujaría al pasado, en cambio, rechaza el lote completo — tocar un programa que ya ha comenzado a emitirse nunca es algo que el sistema absorberá silenciosamente.
5. Un contrato de escritura Lambda-primero, base de datos-segundo. Cuando un ajuste desplaza un programa que ya ha sido desplegado en MediaLive, MongoDB y MediaLive están brevemente desincronizados, y el orden de la conciliación importa. El contrato: Lambda primero, MongoDB segundo. El backend llama a DELETE_PROGRAM en Lambda utilizando los tiempos originales del programa; si alguna llamada falla, el backend lanza un error antes de que ocurra cualquier escritura en la base de datos. Solo una vez que cada llamada de eliminación tiene éxito, una única operación bulkWrite actualiza MongoDB con los nuevos tiempos y restablece isDeployed: false.
Esto produce un invariante limpio: si MongoDB muestra un programa en un nuevo horario, MediaLive ya ha aceptado ese cambio. Si el operador ve un error, ninguno de los sistemas fue tocado. No hay ningún estado posible donde MongoDB y MediaLive discrepen silenciosamente sobre el horario de un programa.
6. El día de reinicio tiene su propia red de seguridad dedicada. "Día de reinicio" elimina todos los programas de un canal para un día de calendario determinado —la operación más destructiva del sistema— por lo que conlleva dos protecciones específicas.
- Un búfer de 2 minutos (
SAFETY_BUFFER_MS = 120000) exime a cualquier programa que comience dentro de los próximos dos minutos, dando a la reproducción en vivo un período de gracia para que un reinicio nunca pueda competir con un programa a punto de salir al aire. - La preservación de arrastre excluye los programas que comenzaron el día anterior pero se extienden hasta hoy; esos pertenecen a la programación de ayer, no a la de hoy.
También está disponible un elegante mecanismo de reserva: si un canal nunca ha sido desplegado en MediaLive, Lambda devuelve una cadena de error específica que el backend entiende, registra como no_infrastructure y luego realiza una eliminación suave solo en MongoDB. El reinicio sigue siendo exitoso; el paso de AWS simplemente se convierte en una operación nula.
Por qué esta combinación de decisiones de diseño
| Decisión | Motivo | Alternativa considerada | Compromiso aceptado |
|---|---|---|---|
| Resolución automática al soltar vs. rechazar y preguntar | Mantiene el arrastrar y soltar utilizable a escala; los cálculos manuales de ajuste no sobreviven a una lista de reproducción creciente | Rechazar el conflicto, pedir al operador que lo solucione | Requiere que el sistema, no el operador, garantice la corrección |
| Mínimo de 6 segundos vs. mínimo de 5 segundos declarado por MediaLive | Absorbe la deriva del reloj entre el backend y AWS, evitando fallos de despliegue intermitentes | Aplicar exactamente 5 segundos | Las pequeñas brechas intencionales (3-4s) se ajustan a cero en lugar de preservarse |
| Recorrido de una sola pasada hacia adelante vs. resolución de conflictos recursiva | Las cascadas se resuelven de forma determinista sin preocupaciones por la profundidad de la recursión | Desplazamiento y verificación recursivos | Requiere una cuidadosa ordenación previa de la línea de tiempo |
| Lambda-primero / DB-segundo vs. DB-primero / Lambda-segundo | Garantiza que MongoDB y MediaLive nunca discrepen silenciosamente | Actualizar MongoDB de forma optimista, sincronizar MediaLive después | Latencia ligeramente mayor por programa desplazado y desplegado, a cambio de cero riesgo de deriva |
Lo que aún se está monitoreando
Una ingeniería honesta significa nombrar las brechas que permanecen abiertas, no solo las que se resuelven.
- Ediciones concurrentes en el mismo canal. Si dos operadores hacen clic en Deploy en el mismo canal en cuestión de unos pocos cientos de milisegundos, ambos cargarán la misma instantánea, ambos ejecutarán el recorrido de ajuste de forma independiente y ambos escribirán en MongoDB. Actualmente no hay un bloqueo por canal ni una verificación de versión optimista. La mitigación actual es operativa —un operador posee un canal a la vez— mientras que la solución técnica, un campo de versión en el documento del canal verificado en el momento de la escritura, está en la hoja de ruta.
- Ausencia de retroalimentación en la UI sobre lo que se ajustó. Cuando el recorrido desplaza un programa tres segundos, la vista del operador se actualiza al estado corregido, pero aún no muestra qué se movió y por qué. Los datos ya existen en la carga útil de la respuesta; se planea una notificación "toast", una barra lateral o una vista de diferencias para la próxima iteración de la UI del programador.
Resultados
- Los operadores pueden soltar un programa en cualquier lugar de la línea de tiempo, y el sistema hace que la programación resultante sea legal en una sola pasada determinista, sin modales de conflicto, sin muros de error, sin cálculos manuales de ajuste.
- Un único algoritmo de cuatro reglas cubre todos los casos —nuevo contra nuevo, nuevo contra existente y desplazamientos en cascada a través de los límites del día— sin tratar ninguno de ellos de forma especial.
- Los programas que cruzan la medianoche y las transiciones de horario de verano se gestionan mediante la misma consulta de superposición de rangos de tiempo que cualquier otro caso; no hay una "ruta de código de medianoche" separada que mantener.
- El día de reinicio no puede, por accidente, sacar un programa en vivo del aire — el búfer de 2 minutos y la preservación de arrastre se aplican a cada canal, en todo momento.
- El contrato Lambda-primero / base de datos-segundo hace imposible la deriva silenciosa de la programación: MongoDB y MediaLive tienen la garantía de estar de acuerdo, o el operador ve un error explícito.
MIN_NEIGHBOUR_GAP_MSes el único control ajustable. Cada margen de seguridad, cada regla de ajuste y cada ventana de superposición lo referencia, por lo que ajustar la definición de "legal" de la plataforma es un cambio de una sola línea.
Consideraciones finales
Lo más interesante de este sistema no es una sola regla, sino lo pocas reglas que fueron necesarias. Cuatro condiciones sobre un valor de brecha, aplicadas en una sola pasada hacia adelante, cubren cada conflicto que un operador puede crear, incluyendo cascadas de múltiples pasos a través de los límites de la medianoche. Ese es un resultado de diseño deliberado: la complejidad se centró en definir correctamente las reglas una sola vez, en lugar de manejar una lista cada vez mayor de casos especiales.
La lección más amplia se generaliza más allá del software de programación: cuando un sistema tiene restricciones externas estrictas —el espaciado mínimo de un codificador, las garantías de consistencia de una base de datos, una transmisión en vivo que no se puede "desemitir"— el lugar más seguro para aplicar esas restricciones es en un pequeño número de reglas deterministas aplicadas consistentemente, no en un manejo ad hoc disperso por todo el código. Y cuando dos sistemas de registro (aquí, MongoDB y MediaLive) deben permanecer sincronizados, ordenar las escrituras de modo que un fallo siempre los deje en un estado conocido y de acuerdo, vale la latencia adicional que cuesta.
Acerca de MicrocosmWorks
En MicrocosmWorks, construimos software de calidad de producción para organizaciones que resuelven problemas complejos de ingeniería.
Nuestra experiencia incluye aplicaciones de AI, plataformas SaaS, software empresarial, sistemas cloud-native, tecnología de medios y arquitectura backend personalizada.
A través de nuestro blog de ingeniería, compartimos lecciones prácticas aprendidas del diseño y la operación de sistemas de producción del mundo real.
Continuar leyendo
Si disfrutaste este artículo, también podrías encontrar útiles estos temas:

