(function () {
	'use strict';

	var LUNCH_START = 12 * 60;
	var LUNCH_END = 13 * 60;
	var scheduleIncludeLunch = true;

	function normalizeIncludeLunch(value) {
		if (value === undefined || value === null) {
			return true;
		}
		return value !== false && value !== 0 && value !== '0';
	}

	function normalizeLunchMinutes(value, fallback) {
		var minutes = parseInt(value, 10);
		if (!Number.isFinite(minutes)) {
			return fallback;
		}
		return Math.max(0, Math.min(24 * 60 - 1, minutes));
	}

	function syncLunchTimesToModule(lunchStart, lunchEnd) {
		LUNCH_START = normalizeLunchMinutes(lunchStart, 720);
		LUNCH_END = normalizeLunchMinutes(lunchEnd, 780);
		if (LUNCH_END <= LUNCH_START) {
			LUNCH_END = Math.min(LUNCH_START + 60, 24 * 60 - 1);
		}
		return {
			lunch_start: LUNCH_START,
			lunch_end: LUNCH_END
		};
	}
	var SAVE_DELAY = 800;
	var MAX_SCHEDULE_WEEKS = 16;
	var PLANEACION_TIMEZONE = 'America/Bogota';
	var WEEKDAY_LABELS = ['Lunes', 'Martes', 'Mi?rcoles', 'Jueves', 'Viernes', 'S?bado', 'Domingo'];
	var TAMANOS_BASE = 1000;
	var MINUTES_BASE = 60;
	var COMPLEXITY_FACTORS = {
		normal: 1,
		media: 1.25,
		alta: 1.5,
		muy_alta: 2
	};
	var LABOR_TYPES = {};
	var LABOR_DEFAULTS = {};
	var LABOR_FIELD_RULES = {};
	var PAPEL_MATERIALS = {};
	var CHECKLIST_FIELDS = ['montaje', 'prep_tinta', 'planchas', 'meta'];
	var TINTAS_COLOR_KEYS = ['cyan', 'magenta', 'amarillo', 'negro', 'otro'];

	function defaultTintasSideConfig(active) {
		return {
			active: !!active,
			cyan: false,
			magenta: false,
			amarillo: false,
			negro: false,
			otro: false,
			pantones: ['', '', '', '', ''],
			mezcla: ''
		};
	}

	function defaultTintasConfig() {
		return {
			count_tiro: '',
			count_retiro: '',
			tiros_x_color: '',
			pasadas: '',
			total_tiraje: '',
			num_planchas: '',
			tiro: defaultTintasSideConfig(false),
			retiro: defaultTintasSideConfig(false)
		};
	}

	function ensureTintasConfig(task) {
		var config = task && task.tintas_config && typeof task.tintas_config === 'object'
			? task.tintas_config
			: null;
		var clean = defaultTintasConfig();

		if (!config) {
			if (task && task.tintas) {
				clean.count_tiro = String(task.tintas).trim();
			}
			if (task && task.colores) {
				clean.tiros_x_color = String(task.colores).trim();
			}
			return syncTintasActiveFromCounts(clean);
		}

		clean.count_tiro = config.count_tiro != null ? String(config.count_tiro) : '';
		clean.count_retiro = config.count_retiro != null ? String(config.count_retiro) : '';
		clean.tiros_x_color = config.tiros_x_color != null ? String(config.tiros_x_color) : '';
		clean.pasadas = config.pasadas != null ? String(config.pasadas) : '';
		clean.total_tiraje = config.total_tiraje != null ? String(config.total_tiraje) : '';
		clean.num_planchas = config.num_planchas != null ? String(config.num_planchas) : '';

		['tiro', 'retiro'].forEach(function (sideKey) {
			var side = config[sideKey] && typeof config[sideKey] === 'object' ? config[sideKey] : {};
			var target = clean[sideKey];
			target.active = !!side.active;
			TINTAS_COLOR_KEYS.forEach(function (colorKey) {
				target[colorKey] = !!side[colorKey];
			});
			target.pantones = [];
			for (var i = 0; i < 5; i++) {
				target.pantones[i] = side.pantones && side.pantones[i] != null ? String(side.pantones[i]) : '';
			}
			target.mezcla = side.mezcla != null ? String(side.mezcla) : '';
		});

		syncTintasActiveFromCounts(clean);

		return clean;
	}

	function parseTintasCount(value) {
		var raw = String(value || '').trim().replace(',', '.');
		if (!raw) {
			return 0;
		}

		var num = parseFloat(raw);
		return isFinite(num) && num > 0 ? num : 0;
	}

	function syncTintasActiveFromCounts(config) {
		config.tiro.active = parseTintasCount(config.count_tiro) > 0;
		config.retiro.active = parseTintasCount(config.count_retiro) > 0;
		return config;
	}

	function isTintasSideEnabled(panel, sideKey) {
		if (!panel) {
			return false;
		}

		var countField = panel.querySelector('[data-planeacion-tintas-field="count_' + sideKey + '"]');
		return parseTintasCount(countField ? countField.value : '') > 0;
	}

	function syncTintasSidesFromCounts(panel) {
		if (!panel) {
			return;
		}

		['tiro', 'retiro'].forEach(function (sideKey) {
			var activeField = panel.querySelector('[data-planeacion-tintas-side-active="' + sideKey + '"]');
			var enabled = isTintasSideEnabled(panel, sideKey);
			if (activeField) {
				activeField.checked = enabled;
			}
			updateTintasSideState(panel, sideKey, enabled);
		});
	}

	function formatTintasSummaries(config) {
		config = ensureTintasConfig({ tintas_config: config });
		var tintasParts = [];
		var coloresParts = [];
		var countTiro = String(config.count_tiro || '').trim();
		var countRetiro = String(config.count_retiro || '').trim();

		if (countTiro || countRetiro) {
			tintasParts.push((countTiro || '0') + ' ? ' + (countRetiro || '0'));
		}

		[
			{ key: 'tiros_x_color', label: 'Tiros?Color' },
			{ key: 'pasadas', label: 'Pasadas' },
			{ key: 'total_tiraje', label: 'Tiraje' },
			{ key: 'num_planchas', label: 'Planchas' }
		].forEach(function (item) {
			var value = String(config[item.key] || '').trim();
			if (value) {
				tintasParts.push(item.label + ': ' + value);
			}
		});

		var colorLabels = {
			cyan: 'C',
			magenta: 'M',
			amarillo: 'A',
			negro: 'N',
			otro: 'Otro'
		};

		[
			{ key: 'tiro', label: 'Tiro' },
			{ key: 'retiro', label: 'Retiro' }
		].forEach(function (sideItem) {
			var side = config[sideItem.key] || defaultTintasSideConfig(false);
			if (!side.active) {
				return;
			}

			var selected = [];
			TINTAS_COLOR_KEYS.forEach(function (colorKey) {
				if (side[colorKey]) {
					selected.push(colorLabels[colorKey]);
				}
			});

			var pantones = side.otro
				? (side.pantones || []).map(function (value) {
					return String(value || '').trim();
				}).filter(Boolean)
				: [];

			var sideBits = [];
			if (selected.length) {
				sideBits.push(selected.join('+'));
			}
			if (pantones.length) {
				sideBits.push('Pantone: ' + pantones.join(', '));
			}
			if (String(side.mezcla || '').trim()) {
				sideBits.push('Mezcla: ' + String(side.mezcla).trim());
			}

			coloresParts.push(sideBits.length ? sideItem.label + ' ' + sideBits.join(' ? ') : sideItem.label);
		});

		return {
			tintas: tintasParts.join(' | '),
			colores: coloresParts.join(' | ')
		};
	}

	function getTintasPanel() {
		var taskForm = getTaskForm();
		return taskForm ? taskForm.querySelector('[data-planeacion-labor-section="tintas_colores"]') : null;
	}

	function readTintasConfigFromForm() {
		var panel = getTintasPanel();
		var config = defaultTintasConfig();
		if (!panel) {
			return config;
		}

		panel.querySelectorAll('[data-planeacion-tintas-field]').forEach(function (field) {
			var key = field.getAttribute('data-planeacion-tintas-field');
			if (key) {
				config[key] = field.value;
			}
		});

		['tiro', 'retiro'].forEach(function (sideKey) {
			TINTAS_COLOR_KEYS.forEach(function (colorKey) {
				var colorField = panel.querySelector('[data-planeacion-tintas-color="' + sideKey + ':' + colorKey + '"]');
				config[sideKey][colorKey] = colorField ? !!colorField.checked : false;
			});

			for (var i = 1; i <= 5; i++) {
				var pantoneField = panel.querySelector('[data-planeacion-tintas-pantone="' + sideKey + ':' + i + '"]');
				config[sideKey].pantones[i - 1] = pantoneField ? pantoneField.value : '';
			}

			var mezclaField = panel.querySelector('[data-planeacion-tintas-mezcla="' + sideKey + '"]');
			config[sideKey].mezcla = mezclaField ? mezclaField.value : '';

			if (!config[sideKey].otro) {
				config[sideKey].pantones = ['', '', '', '', ''];
			}
		});

		return syncTintasActiveFromCounts(config);
	}

	function applyTintasConfigToForm(config) {
		var panel = getTintasPanel();
		config = ensureTintasConfig({ tintas_config: config });
		if (!panel) {
			return;
		}

		panel.querySelectorAll('[data-planeacion-tintas-field]').forEach(function (field) {
			var key = field.getAttribute('data-planeacion-tintas-field');
			if (key && config[key] != null) {
				field.value = config[key];
			}
		});

		['tiro', 'retiro'].forEach(function (sideKey) {
			var side = config[sideKey] || defaultTintasSideConfig(false);
			var activeField = panel.querySelector('[data-planeacion-tintas-side-active="' + sideKey + '"]');
			if (activeField) {
				activeField.checked = !!side.active;
			}

			TINTAS_COLOR_KEYS.forEach(function (colorKey) {
				var colorField = panel.querySelector('[data-planeacion-tintas-color="' + sideKey + ':' + colorKey + '"]');
				if (colorField) {
					colorField.checked = !!side[colorKey];
				}
			});

			for (var i = 1; i <= 5; i++) {
				var pantoneField = panel.querySelector('[data-planeacion-tintas-pantone="' + sideKey + ':' + i + '"]');
				if (pantoneField) {
					pantoneField.value = side.pantones && side.pantones[i - 1] != null ? side.pantones[i - 1] : '';
				}
			}

			var mezclaField = panel.querySelector('[data-planeacion-tintas-mezcla="' + sideKey + '"]');
			if (mezclaField) {
				mezclaField.value = side.mezcla || '';
			}
		});

		syncTintasSidesFromCounts(panel);
	}

	function updateTintasPantonesState(panel, sideKey, sideEnabledOverride) {
		if (!panel) {
			return;
		}

		var sideWrap = panel.querySelector('[data-planeacion-tintas-side="' + sideKey + '"]');
		if (!sideWrap) {
			return;
		}

		var sideEnabled = typeof sideEnabledOverride === 'boolean'
			? sideEnabledOverride
			: isTintasSideEnabled(panel, sideKey);
		var otroField = panel.querySelector('[data-planeacion-tintas-color="' + sideKey + ':otro"]');
		var pantonesEnabled = sideEnabled && otroField && otroField.checked;
		var pantonesWrap = sideWrap.querySelector('.logistica-planeacion__tintas-pantones');

		if (pantonesWrap) {
			pantonesWrap.classList.toggle('is-enabled', pantonesEnabled);
		}

		sideWrap.querySelectorAll('[data-planeacion-tintas-pantone]').forEach(function (field) {
			field.disabled = !pantonesEnabled;
		});
	}

	function updateTintasSideState(panel, sideKey, enabledOverride) {
		if (!panel) {
			return;
		}

		var sideWrap = panel.querySelector('[data-planeacion-tintas-side="' + sideKey + '"]');
		var activeField = panel.querySelector('[data-planeacion-tintas-side-active="' + sideKey + '"]');
		if (!sideWrap || !activeField) {
			return;
		}

		var enabled = typeof enabledOverride === 'boolean'
			? enabledOverride
			: isTintasSideEnabled(panel, sideKey);
		activeField.checked = enabled;
		sideWrap.classList.toggle('is-active', enabled);

		sideWrap.querySelectorAll('[data-planeacion-tintas-color]').forEach(function (field) {
			field.disabled = !enabled;
		});

		var mezclaField = sideWrap.querySelector('[data-planeacion-tintas-mezcla="' + sideKey + '"]');
		if (mezclaField) {
			mezclaField.disabled = !enabled;
		}

		updateTintasPantonesState(panel, sideKey, enabled);
	}

	function syncAllTintasSideStates(panel) {
		syncTintasSidesFromCounts(panel || getTintasPanel());
	}

	function bindTintasPanelEvents() {
		var panel = getTintasPanel();
		if (!panel) {
			return;
		}

		if (panel.dataset.planeacionTintasBound !== '1') {
			panel.dataset.planeacionTintasBound = '1';
			panel.addEventListener('input', function (event) {
				var target = event.target;
				if (!target || !panel.contains(target)) {
					return;
				}

				var countField = target.getAttribute('data-planeacion-tintas-field');
				if (countField === 'count_tiro' || countField === 'count_retiro') {
					syncTintasSidesFromCounts(panel);
				}
			});
			panel.addEventListener('change', function (event) {
				var target = event.target;
				if (!target || !panel.contains(target)) {
					return;
				}

				var countField = target.getAttribute('data-planeacion-tintas-field');
				if (countField === 'count_tiro' || countField === 'count_retiro') {
					syncTintasSidesFromCounts(panel);
					return;
				}

				var sideActive = target.getAttribute('data-planeacion-tintas-side-active');
				if (sideActive) {
					var countInput = panel.querySelector('[data-planeacion-tintas-field="count_' + sideActive + '"]');
					if (countInput) {
						if (target.checked && parseTintasCount(countInput.value) <= 0) {
							countInput.value = '1';
						} else if (!target.checked) {
							countInput.value = '0';
						}
					}
					syncTintasSidesFromCounts(panel);
					return;
				}

				var colorKey = target.getAttribute('data-planeacion-tintas-color');
				if (colorKey) {
					var sideFromColor = colorKey.split(':')[0];
					if (sideFromColor) {
						updateTintasPantonesState(panel, sideFromColor);
						if (!target.checked && colorKey.endsWith(':otro')) {
							var sideWrap = panel.querySelector('[data-planeacion-tintas-side="' + sideFromColor + '"]');
							if (sideWrap) {
								sideWrap.querySelectorAll('[data-planeacion-tintas-pantone]').forEach(function (field) {
									field.value = '';
								});
							}
						}
					}
				}
			});
		}

		syncAllTintasSideStates(panel);
	}

	function syncTintasSummariesOnTask(task) {
		var config = ensureTintasConfig(task);
		var summaries = formatTintasSummaries(config);
		task.tintas_config = config;
		task.tintas = summaries.tintas;
		task.colores = summaries.colores;
		return task;
	}

	function defaultLaborFieldRules() {
		return {
			impresion: {
				fields: ['tamanos', 'complexity', 'tintas', 'papel', 'montaje', 'prep_tinta', 'planchas', 'meta'],
				auto_duration: true,
				hint: 'Tiraje, tintas (tiro/retiro), colores y papel. La duraci?n se calcula: 1000 tama?os ? 60 min seg?n complejidad.'
			},
			prep_tinta: {
				fields: ['tintas', 'meta'],
				auto_duration: false,
				hint: 'Indique tintas, colores y pantones a preparar. Duraci?n editable manualmente.'
			},
			prep_sig_trabajo: {
				fields: ['meta'],
				auto_duration: false,
				hint: 'Indique la duraci?n y el cumplimiento de metas para el siguiente trabajo.'
			},
			limpieza_mantenimiento: {
				fields: ['meta'],
				auto_duration: false,
				hint: 'Duraci?n sugerida seg?n el tipo de labor. Ajuste si es necesario.'
			},
			lavado_maquina: {
				fields: ['meta'],
				auto_duration: false,
				hint: 'Duraci?n sugerida para lavado de m?quina. Ajuste si es necesario.'
			},
			reunion: {
				fields: ['meta'],
				auto_duration: false,
				hint: 'Duraci?n de la reuni?n. Ajuste si es necesario.'
			},
			otro: {
				fields: ['meta'],
				auto_duration: false,
				hint: 'Describe la actividad y ajusta la duraci?n.'
			}
		};
	}

	function normalizeLaborFieldRules(raw) {
		var defaults = defaultLaborFieldRules();
		if (!raw || typeof raw !== 'object') {
			return defaults;
		}

		Object.keys(defaults).forEach(function (key) {
			if (!raw[key] || typeof raw[key] !== 'object') {
		return;
	}

			if (Array.isArray(raw[key].fields)) {
				defaults[key].fields = raw[key].fields.slice();
			}
			if (typeof raw[key].auto_duration === 'boolean') {
				defaults[key].auto_duration = raw[key].auto_duration;
			}
			if (typeof raw[key].hint === 'string' && raw[key].hint) {
				defaults[key].hint = raw[key].hint;
			}
		});

		return defaults;
	}

	function defaultFieldOptions() {
		return {
			papel_materials: {
				P1: 'P1',
				P2: 'P2',
				GCU: 'GCU',
				otro: 'Otro'
			},
			montaje: ['OK', 'BUSCAR', 'PLANCHAS PEDIDAS', 'N/A'],
			prep_tinta: ['OK', 'BUSCAR', 'PLANCHAS PEDIDAS', 'N/A'],
			planchas: ['OK', 'BUSCAR', 'PLANCHAS PEDIDAS', 'N/A'],
			meta: ['OK', 'BUSCAR', 'PLANCHAS PEDIDAS', 'N/A']
		};
	}

	function normalizeFieldOptions(raw) {
		var defaults = defaultFieldOptions();
		var options = defaults;

		if (!raw || typeof raw !== 'object') {
			return options;
		}

		if (raw.papel_materials && typeof raw.papel_materials === 'object') {
			options.papel_materials = raw.papel_materials;
		}

		CHECKLIST_FIELDS.forEach(function (field) {
			if (Array.isArray(raw[field]) && raw[field].length) {
				options[field] = raw[field].slice();
			}
		});

		return options;
	}

	function loadFieldOptionsConfig(data) {
		var options = normalizeFieldOptions(data && data.field_options ? data.field_options : null);
		loadPapelConfig({ papel_materials: options.papel_materials });
		return options;
	}

	function loadLaborConfig(data) {
		LABOR_TYPES = (data && data.labor_types) ? data.labor_types : {
			impresion: 'Impresi?n',
			prep_tinta: 'Preparaci?n de tinta',
			limpieza_mantenimiento: 'Limpieza y mantenimiento',
			prep_sig_trabajo: 'Preparaci?n siguiente trabajo',
			lavado_maquina: 'Lavado de m?quina',
			reunion: 'Reuni?n impresi?n y dise?o',
			otro: 'Otra actividad'
		};
		LABOR_DEFAULTS = (data && data.labor_defaults) ? data.labor_defaults : {
			impresion: 60,
			prep_tinta: 60,
			limpieza_mantenimiento: 90,
			prep_sig_trabajo: 60,
			lavado_maquina: 60,
			reunion: 60,
			otro: 60
		};
		LABOR_FIELD_RULES = normalizeLaborFieldRules(data && data.labor_field_rules ? data.labor_field_rules : null);
	}

	function loadPapelConfig(data) {
		PAPEL_MATERIALS = (data && data.papel_materials) ? data.papel_materials : {
			P1: 'P1',
			P2: 'P2',
			GCU: 'GCU',
			otro: 'Otro'
		};
	}

	function parseLegacyPapel(legacy) {
		var result = {
			ancho: '',
			largo: '',
			gramaje: '',
			material: '',
			material_otro: ''
		};
		var text = String(legacy || '').trim();
		if (!text) {
			return result;
		}

		var sizeMatch = text.match(/^([\d.,]+)\s*[xX?]\s*([\d.,]+)/);
		if (sizeMatch) {
			result.ancho = sizeMatch[1].replace(',', '.');
			result.largo = sizeMatch[2].replace(',', '.');
		}

		var gramajeMatch = text.match(/\b(\d{2,4})\s*(?:g|gr|gramaje)?\b/i);
		if (gramajeMatch) {
			result.gramaje = gramajeMatch[1];
		} else {
			var shortGramajeMatch = text.match(/[xX?]\s*[\d.,]+\s+(\d{1,2})\b/);
			if (shortGramajeMatch) {
				result.gramaje = shortGramajeMatch[1];
			}
		}

		var materialMatch = text.match(/\b(P1S?|P2S?|GCU\w*)\b/i);
		if (materialMatch) {
			var token = materialMatch[1].toUpperCase();
			if (token.indexOf('P1') === 0) {
				result.material = 'P1';
			} else if (token.indexOf('P2') === 0) {
				result.material = 'P2';
			} else if (token.indexOf('GCU') === 0) {
				result.material = 'GCU';
			}
		}

		return result;
	}

	function formatPapelSummary(fields) {
		var parts = [];
		var ancho = String(fields.papel_ancho || fields.ancho || '').trim();
		var largo = String(fields.papel_largo || fields.largo || '').trim();
		var gramaje = String(fields.papel_gramaje || fields.gramaje || '').trim();
		var material = String(fields.papel_material || fields.material || '').trim();
		var materialOtro = String(fields.papel_material_otro || fields.material_otro || '').trim();

		if (ancho && largo) {
			parts.push(ancho + ' x ' + largo + ' cm');
		} else if (ancho || largo) {
			parts.push((ancho + ' ' + largo).trim());
		}

		if (gramaje) {
			parts.push(gramaje + ' g');
		}

		if (material === 'otro' && materialOtro) {
			parts.push(materialOtro);
		} else if (material && PAPEL_MATERIALS[material]) {
			parts.push(PAPEL_MATERIALS[material]);
		}

		return parts.join(' ? ');
	}

	function normalizePapelFields(task) {
		if (!task) {
			return task;
		}

		if (task.papel_ancho || task.papel_largo || task.papel_gramaje || task.papel_material) {
			task.papel = formatPapelSummary(task);
			return task;
		}

		if (!task.papel) {
			task.papel_ancho = task.papel_ancho || '';
			task.papel_largo = task.papel_largo || '';
			task.papel_gramaje = task.papel_gramaje || '';
			task.papel_material = task.papel_material || '';
			task.papel_material_otro = task.papel_material_otro || '';
			return task;
		}

		var parsed = parseLegacyPapel(task.papel);
		task.papel_ancho = parsed.ancho;
		task.papel_largo = parsed.largo;
		task.papel_gramaje = parsed.gramaje;
		task.papel_material = parsed.material;
		task.papel_material_otro = parsed.material_otro;
		task.papel = formatPapelSummary(task);
		return task;
	}

	function sanitizeLaborType(value) {
		var key = String(value || 'otro');
		return Object.prototype.hasOwnProperty.call(LABOR_TYPES, key) ? key : 'otro';
	}

	function getLaborFieldRules(laborType) {
		var key = sanitizeLaborType(laborType);
		return LABOR_FIELD_RULES[key] || LABOR_FIELD_RULES.otro || defaultLaborFieldRules().otro;
	}

	function laborFieldVisible(laborType, fieldKey) {
		var rules = getLaborFieldRules(laborType);
		return rules.fields.indexOf(fieldKey) !== -1;
	}

	function usesAutoDurationFromTamanos(laborType) {
		return !!getLaborFieldRules(laborType).auto_duration;
	}

	function isImpresionLabor(laborType) {
		return sanitizeLaborType(laborType) === 'impresion';
	}

	function isPrepTintaLabor(laborType) {
		return sanitizeLaborType(laborType) === 'prep_tinta';
	}

	function getLaborLabel(laborType) {
		return LABOR_TYPES[sanitizeLaborType(laborType)] || 'Otra actividad';
	}

	function getDefaultLaborMinutes(laborType) {
		var key = sanitizeLaborType(laborType);
		var minutes = parseInt(LABOR_DEFAULTS[key], 10);
		return Number.isFinite(minutes) && minutes > 0 ? minutes : 60;
	}

	function roundDuration(minutes) {
		minutes = Math.max(15, parseInt(minutes, 10) || 60);
		return Math.round(minutes / 15) * 15;
	}

	function parseTamanos(value) {
		var text = String(value || '').trim().toUpperCase();
		if (!text) {
			return null;
		}

		text = text.replace(/\s*TIR\b.*/i, '').trim();
		var match = text.match(/^([\d.,]+)/);
		if (!match) {
			return null;
		}

		var num = match[1];
		if (/^\d{1,3}\.\d{3}$/.test(num)) {
			return parseInt(num.replace('.', ''), 10);
		}

		var normalized = num.replace(/\./g, '').replace(',', '.');
		var parsed = Number.parseFloat(normalized);
		if (!Number.isFinite(parsed) || parsed <= 0) {
			return null;
		}

		return Math.round(parsed);
	}

	function durationFromTamanos(tamanosCount, complexity) {
		if (!tamanosCount || tamanosCount <= 0) {
			return null;
		}

		var base = TAMANOS_BASE || 1000;
		var factor = COMPLEXITY_FACTORS[complexity] || 1;
		var minutes = (tamanosCount / base) * (MINUTES_BASE || 60) * factor;
		minutes = Math.round(minutes / 15) * 15;
		return Math.max(15, minutes);
	}

	function inferLaborTypeFromTask(task) {
		if (task && task.labor_type) {
			return sanitizeLaborType(task.labor_type);
		}

		var labor = String(task && task.labor ? task.labor : '').toLowerCase();
		if (labor.indexOf('lavado') >= 0) {
			return 'lavado_maquina';
		}
		if (labor.indexOf('prepa tinta') >= 0 || labor.indexOf('preparacion tinta') >= 0 || labor.indexOf('preparaci?n tinta') >= 0) {
			return 'prep_tinta';
		}
		if (labor.indexOf('limpieza') >= 0 || labor.indexOf('mantenimiento') >= 0 || labor.indexOf('aseo') >= 0) {
			return 'limpieza_mantenimiento';
		}
		if (labor.indexOf('sig. trabajo') >= 0 || labor.indexOf('siguiente trabajo') >= 0 || labor.indexOf('preparacion sig') >= 0) {
			return 'prep_sig_trabajo';
		}
		if (labor.indexOf('reunion') >= 0 || labor.indexOf('reuni?n') >= 0) {
			return 'reunion';
		}
		if (parseTamanos(task && task.tamanos)) {
			return 'impresion';
		}
		return 'otro';
	}

	function getTaskForm() {
		return document.querySelector('[data-planeacion-form]');
	}

	function setFormFieldValue(key, value) {
		var taskForm = getTaskForm();
		if (!taskForm) {
			return;
		}

		var field = taskForm.querySelector('[data-planeacion-field="' + key + '"]');
		if (field) {
			field.value = value;
		}
	}

	function stripHiddenLaborFields(task) {
		if (!task) {
			return task;
		}

		var laborType = sanitizeLaborType(task.labor_type || inferLaborTypeFromTask(task));

		if (!laborFieldVisible(laborType, 'tamanos')) {
			task.tamanos = '';
			task.complexity = 'normal';
		}

		if (!laborFieldVisible(laborType, 'tintas')) {
			task.tintas = '';
			task.colores = '';
			task.tintas_config = defaultTintasConfig();
		}

		if (!laborFieldVisible(laborType, 'papel')) {
			task.papel = '';
			task.papel_ancho = '';
			task.papel_largo = '';
			task.papel_gramaje = '';
			task.papel_material = '';
			task.papel_material_otro = '';
		}

		CHECKLIST_FIELDS.forEach(function (key) {
			if (!laborFieldVisible(laborType, key)) {
				task[key] = '';
			}
		});

		return task;
	}

	function stripHiddenLaborFormFields(laborType) {
		if (!getTaskForm()) {
			return;
		}

		if (!laborFieldVisible(laborType, 'tamanos')) {
			setFormFieldValue('tamanos', '');
			setFormFieldValue('complexity', 'normal');
		}

		if (!laborFieldVisible(laborType, 'tintas')) {
			applyTintasConfigToForm(defaultTintasConfig());
		}

		if (!laborFieldVisible(laborType, 'papel')) {
			['papel_ancho', 'papel_largo', 'papel_gramaje', 'papel_material', 'papel_material_otro'].forEach(function (key) {
				setFormFieldValue(key, '');
			});
		}

		CHECKLIST_FIELDS.forEach(function (key) {
			if (!laborFieldVisible(laborType, key)) {
				setFormFieldValue(key, '');
			}
		});
	}

	function normalizeTaskLabor(task) {
		task.labor_type = inferLaborTypeFromTask(task);
		syncTintasSummariesOnTask(task);
		stripHiddenLaborFields(task);
		return task;
	}

	function resolveTaskDuration(task) {
		normalizeTaskLabor(task);
		var laborType = sanitizeLaborType(task.labor_type || 'impresion');
		var result = getDefaultLaborMinutes(laborType);

		if (usesAutoDurationFromTamanos(laborType)) {
			var count = parseTamanos(task.tamanos);
			var auto = durationFromTamanos(count, task.complexity || 'normal');
			if (auto !== null) {
				return auto;
			}
		}

		if (task.duration_minutes) {
			result = roundDuration(task.duration_minutes);
		}

		return Number.isFinite(result) ? result : 60;
	}

	function applyDurationToTask(task) {
		task.duration_minutes = resolveTaskDuration(task);
		return task;
	}

	function formatDurationLabel(minutes) {
		minutes = Math.max(0, Math.round(Number(minutes) || 0));
		if (minutes >= 60) {
		var h = Math.floor(minutes / 60);
		var m = minutes % 60;
			return h + ' h ' + m + ' min';
		}
		return minutes + ' min';
	}

	function adminConfig() {
		return window.LogisticaAdmin || {};
	}

	function buildPlaneacionExportUrl(semanaInicio, machine) {
		var config = adminConfig();
		var base = config.adminPostUrl || '/wp-admin/admin-post.php';
		var params = [
			'action=logistica_export_planeacion_impresion',
			'semana_inicio=' + encodeURIComponent(semanaInicio || ''),
			'maquina=' + encodeURIComponent(machine || 'sm74'),
			'_wpnonce=' + encodeURIComponent(config.planeacionExportNonce || '')
		];

		return base + (base.indexOf('?') === -1 ? '?' : '&') + params.join('&');
	}

	function bootstrapData() {
		var node = document.getElementById('logistica-planeacion-bootstrap');
		if (!node) {
			return {};
		}

		try {
			var data = JSON.parse(node.textContent || '{}');
			if (data.tamanos_base) {
				TAMANOS_BASE = data.tamanos_base;
			}
			if (data.minutes_base) {
				MINUTES_BASE = data.minutes_base;
			}
			loadLaborConfig(data);
			loadPapelConfig(data);
			return data;
		} catch (error) {
			return {};
		}
	}

	function planeacionTimezone() {
		var bootstrap = bootstrapData();
		return (bootstrap && bootstrap.timezone) ? bootstrap.timezone : PLANEACION_TIMEZONE;
	}

	function formatPlaneacionTimeHms(dateMs) {
		var ms = dateMs !== undefined && dateMs !== null ? dateMs : Date.now();
		var parts = {};

		new Intl.DateTimeFormat('en-GB', {
			timeZone: planeacionTimezone(),
			hour: '2-digit',
			minute: '2-digit',
			second: '2-digit',
			hour12: false
		}).formatToParts(new Date(ms)).forEach(function (part) {
			if (part.type !== 'literal') {
				parts[part.type] = part.value;
			}
		});

		return parts.hour + ':' + parts.minute + ':' + parts.second;
	}

	function timeToMinutes(value) {
		if (value === null || value === undefined || value === '') {
			return null;
		}

		if (typeof value === 'number' && Number.isFinite(value)) {
			return Math.max(0, Math.round(value));
		}

		var text = String(value).trim();
		if (!text) {
			return null;
		}

		var ampm = text.match(/^(\d{1,2}):(\d{2})\s*(a\.?\s*m\.?|p\.?\s*m\.?)$/i);
		if (ampm) {
			var hour = parseInt(ampm[1], 10);
			var minute = parseInt(ampm[2], 10);
			if (/p/i.test(ampm[3]) && hour < 12) {
				hour += 12;
			}
			if (/a/i.test(ampm[3]) && hour === 12) {
				hour = 0;
			}
			return (hour * 60) + minute;
		}

		var parts = text.split(':');
		if (parts.length < 2) {
			return null;
		}

		var h = parseInt(parts[0], 10);
		var m = parseInt(parts[1], 10);
		if (!Number.isFinite(h) || !Number.isFinite(m)) {
			return null;
		}

		return (h * 60) + m;
	}

	function minutesToTime(minutes) {
		if (!Number.isFinite(minutes)) {
			return '?';
		}
		minutes = Math.max(0, Math.round(minutes));
		var h = Math.floor(minutes / 60);
		var m = minutes % 60;
		return h + ':' + String(m).padStart(2, '0');
	}

	function formatRange(start, end) {
		if (!Number.isFinite(start) || !Number.isFinite(end)) {
			if (Number.isFinite(start)) {
				return minutesToTime(start) + ' ? ?';
			}
			return '?';
		}
		if (end < start) {
			end = start;
		}
		return minutesToTime(start) + ' ? ' + minutesToTime(end);
	}

	function skipLunch(cursor) {
		if (!scheduleIncludeLunch) {
			return cursor;
		}
		if (cursor >= LUNCH_START && cursor < LUNCH_END) {
			return LUNCH_END;
		}
		return cursor;
	}

	function advanceCursor(minutes) {
		return skipLunch(minutes);
	}

	function capFinishBeforeLunch(start, finish) {
		if (!scheduleIncludeLunch || !Number.isFinite(start) || !Number.isFinite(finish)) {
			return finish;
		}
		if (start >= LUNCH_END || finish <= LUNCH_START) {
			return finish;
		}
		if (start < LUNCH_START) {
			return Math.min(finish, LUNCH_START);
		}
		return finish;
	}

	function isLunchSegmentGap(fromSeg, toSeg) {
		if (!fromSeg || !toSeg || !scheduleIncludeLunch) {
			return false;
		}
		if (parseInt(fromSeg.day, 10) !== parseInt(toSeg.day, 10)) {
			return false;
		}
		var fromEnd = Number(fromSeg.end_minutes);
		var toStart = Number(toSeg.start_minutes);
		return Number.isFinite(fromEnd) && Number.isFinite(toStart)
			&& fromEnd <= LUNCH_START + 1
			&& toStart >= LUNCH_END - 1;
	}

	function analyzeSegmentContinuationLink(task, segment, displayItem) {
		var segments = (task.schedule_segments || []).slice();
		var idx = typeof displayItem.segmentIndex === 'number' ? displayItem.segmentIndex : -1;

		if (idx < 0 || idx >= segments.length) {
			idx = segments.findIndex(function (s) {
				return parseInt(s.day, 10) === parseInt(segment.day, 10)
					&& Number(s.start_minutes) === Number(segment.start_minutes)
					&& Number(s.end_minutes) === Number(segment.end_minutes);
			});
		}

		var info = {
			isAfterLunch: false,
			continuesAfterLunch: false,
			continuesSameDayLater: false,
			continuesNextDay: false,
			resumeLabel: '',
			cutLabel: ''
		};

		if (idx < 0 || !segments.length) {
			return info;
		}

		var current = segments[idx];
		var prev = idx > 0 ? segments[idx - 1] : null;
		var next = idx < segments.length - 1 ? segments[idx + 1] : null;

		if (prev && isLunchSegmentGap(prev, current)) {
			info.isAfterLunch = true;
			info.resumeLabel = 'Tras almuerzo';
		} else if (segment.is_continuation || (prev && parseInt(prev.day, 10) !== parseInt(current.day, 10))) {
			info.resumeLabel = 'Continuaci?n';
		} else if (prev && parseInt(prev.day, 10) === parseInt(current.day, 10)) {
			info.resumeLabel = 'Continuaci?n';
		}

		if (next) {
			if (isLunchSegmentGap(current, next)) {
				info.continuesAfterLunch = true;
				info.cutLabel = 'Corte almuerzo';
			} else if (parseInt(next.day, 10) === parseInt(current.day, 10)) {
				info.continuesSameDayLater = true;
				info.cutLabel = 'Contin?a';
			} else if (
				parseInt(next.day, 10) > parseInt(current.day, 10)
				|| (parseInt(next.week_offset, 10) || 0) > (parseInt(current.week_offset, 10) || 0)
			) {
				info.continuesNextDay = true;
				info.cutLabel = 'Contin?a ma?ana';
			}
		}

		return info;
	}

	function renderSegmentConnectors(track, items, metrics) {
		if (!track || !metrics) {
			return;
		}

		Array.from(track.querySelectorAll('[data-planeacion-segment-link]')).forEach(function (node) {
			node.remove();
		});

		var grouped = {};

		items.forEach(function (item) {
			if (item.isCarryover) {
				return;
			}
			var taskId = item.task.id;
			if (!grouped[taskId]) {
				grouped[taskId] = [];
			}
			grouped[taskId].push(item);
		});

		Object.keys(grouped).forEach(function (taskId) {
			var taskItems = grouped[taskId].slice().sort(function (a, b) {
				return (a.segment.start_minutes || 0) - (b.segment.start_minutes || 0);
			});

			if (taskItems.length < 2) {
				return;
			}

			for (var i = 0; i < taskItems.length - 1; i++) {
				var fromSeg = taskItems[i].segment;
				var toSeg = taskItems[i + 1].segment;
				var leftPct = minutesToTimelinePercent(Number(fromSeg.end_minutes), metrics);
				var rightPct = minutesToTimelinePercent(Number(toSeg.start_minutes), metrics);
				var width = Math.max(0.35, rightPct - leftPct);

				if (width <= 0.35) {
					continue;
				}

				var link = document.createElement('div');
				link.className = 'logistica-planeacion__timeline-segment-link';
				link.setAttribute('data-planeacion-segment-link', '');

				if (isLunchSegmentGap(fromSeg, toSeg)) {
					link.classList.add('logistica-planeacion__timeline-segment-link--lunch');
					link.title = 'La actividad contin?a despu?s del almuerzo';
					link.setAttribute('aria-label', 'Contin?a tras el almuerzo');
				} else if (parseInt(fromSeg.day, 10) === parseInt(toSeg.day, 10)) {
					link.classList.add('logistica-planeacion__timeline-segment-link--same-day');
					link.title = 'Continuaci?n de la misma actividad';
					link.setAttribute('aria-label', 'Continuaci?n misma actividad');
				}

				link.style.left = leftPct + '%';
				link.style.width = width + '%';
				if (window.LogisticaPlaneacionTaskColors) {
					LogisticaPlaneacionTaskColors.applySegmentLinkColor(link, taskId, taskItems[0].weekOffset || 0);
				}
				track.appendChild(link);
			}
		});
	}

	function getTimelineMetrics(jornadaStart, jornadaEnd) {
		var start = Number(jornadaStart);
		var end = Number(jornadaEnd);
		if (!Number.isFinite(start)) {
			start = 420;
		}
		if (!Number.isFinite(end) || end <= start) {
			end = start + 600;
		}
		return {
			start: start,
			end: end,
			span: end - start
		};
	}

	function minutesToTimelinePercent(minutes, metrics) {
		var value = Number(minutes);
		if (!Number.isFinite(value)) {
			return 0;
		}
		value = Math.max(metrics.start, Math.min(metrics.end, value));
		return ((value - metrics.start) / metrics.span) * 100;
	}

	function rangeToTimelinePercent(start, end, metrics) {
		var left = minutesToTimelinePercent(start, metrics);
		var right = minutesToTimelinePercent(end, metrics);
		return {
			left: left,
			width: Math.max(0.35, right - left)
		};
	}

	function computeTimelineFreeSlots(items, metrics) {
		var occupied = (items || []).map(function (item) {
			var segment = item.segment || {};
			return {
				start: Number(segment.start_minutes),
				end: Number(segment.end_minutes)
			};
		}).filter(function (block) {
			return Number.isFinite(block.start) && Number.isFinite(block.end) && block.end > block.start;
		}).sort(function (a, b) {
			return a.start - b.start;
		});

		var slots = [];
		var cursor = metrics.start;

		occupied.forEach(function (block) {
			if (block.start > cursor) {
				slots.push({ start: cursor, end: block.start });
			}
			cursor = Math.max(cursor, block.end);
		});

		if (cursor < metrics.end) {
			slots.push({ start: cursor, end: metrics.end });
		}

		return excludeLunchFromSlots(slots);
	}

	function excludeLunchFromSlots(slots) {
		if (!scheduleIncludeLunch) {
			return slots;
		}

		var result = [];

		slots.forEach(function (slot) {
			if (slot.end <= LUNCH_START || slot.start >= LUNCH_END) {
				result.push(slot);
				return;
			}

			if (slot.start < LUNCH_START) {
				result.push({ start: slot.start, end: Math.min(slot.end, LUNCH_START) });
			}

			if (slot.end > LUNCH_END) {
				result.push({ start: Math.max(slot.start, LUNCH_END), end: slot.end });
			}
		});

		return result;
	}

	function renderTimelineRuler(ruler, metrics) {
		if (!ruler) {
			return;
		}

		var ticks = [];
		var tick;

		for (tick = Math.floor(metrics.start / 60) * 60; tick <= metrics.end; tick += 60) {
			var labelMinute = Math.max(metrics.start, tick);
			if (labelMinute > metrics.end) {
				break;
			}
			ticks.push({
				minute: labelMinute,
				pct: minutesToTimelinePercent(labelMinute, metrics)
			});
		}

		var html = '';
		ticks.forEach(function (entry, index) {
			var tickClass = 'logistica-planeacion__timeline-tick';
			if (index === 0) {
				tickClass += ' logistica-planeacion__timeline-tick--start';
			}
			if (index === ticks.length - 1) {
				tickClass += ' logistica-planeacion__timeline-tick--end';
			}
			html += '<span class="' + tickClass + '" style="left:' + entry.pct + '%">' +
				'<span class="logistica-planeacion__timeline-tick-label">' + minutesToTime(entry.minute) + '</span>' +
			'</span>';
		});

		ruler.innerHTML = html;
	}

	function renderTimelineLunchBand(track, metrics) {
		if (!track) {
			return;
		}

		var existing = track.querySelector('[data-planeacion-timeline-lunch]');
		if (existing) {
			existing.remove();
		}

		if (!scheduleIncludeLunch) {
			return;
		}

		if (LUNCH_END <= metrics.start || LUNCH_START >= metrics.end) {
			return;
		}

		var lunchStart = Math.max(metrics.start, LUNCH_START);
		var lunchEnd = Math.min(metrics.end, LUNCH_END);
		var pos = rangeToTimelinePercent(lunchStart, lunchEnd, metrics);
		var lunch = document.createElement('div');
		lunch.className = 'logistica-planeacion__timeline-slot logistica-planeacion__timeline-slot--lunch';
		lunch.setAttribute('data-planeacion-timeline-lunch', '1');
		lunch.style.left = pos.left + '%';
		lunch.style.width = pos.width + '%';
		lunch.title = 'Almuerzo ' + formatRange(lunchStart, lunchEnd);
		lunch.innerHTML =
			'<span class="logistica-planeacion__timeline-slot-label">Almuerzo</span>' +
			'<span class="logistica-planeacion__timeline-slot-hint">' + formatRange(lunchStart, lunchEnd) + '</span>';
		track.appendChild(lunch);
	}

	function renderTimelineFreeSlot(slot, metrics) {
		var pos = rangeToTimelinePercent(slot.start, slot.end, metrics);
		var el = document.createElement('div');
		el.className = 'logistica-planeacion__timeline-slot logistica-planeacion__timeline-slot--free';
		el.setAttribute('data-planeacion-free-slot', '1');
		el.style.left = pos.left + '%';
		el.style.width = pos.width + '%';
		el.title = 'Disponible: ' + formatRange(slot.start, slot.end);
		el.innerHTML =
			'<span class="logistica-planeacion__timeline-slot-label">' + formatRange(slot.start, slot.end) + '</span>' +
			'<span class="logistica-planeacion__timeline-slot-hint">Disponible</span>';
		return el;
	}

	function positionTaskOnTimeline(card, segment, metrics) {
		var start = Number(segment.start_minutes);
		var end = Number(segment.end_minutes);
		if (!Number.isFinite(start) || !Number.isFinite(end)) {
			return;
		}
		var pos = rangeToTimelinePercent(start, end, metrics);
		card.classList.add('logistica-planeacion__task--timeline');
		card.style.left = pos.left + '%';
		card.style.width = pos.width + '%';
	}

	function applyTimelineLayout(zone, dayKey, items, dayHours, days, calendarConfig) {
		var scroll = zone.querySelector('[data-planeacion-timeline-scroll]');
		var timeline = zone.querySelector('[data-planeacion-timeline]');
		var ruler = zone.querySelector('[data-planeacion-timeline-ruler]');
		var track = zone.querySelector('[data-planeacion-timeline-track]');
		if (!scroll || !timeline || !track) {
			return null;
		}

		if (isDayNonWorking(dayKey, dayHours, days, calendarConfig)) {
			scroll.hidden = true;
			if (ruler) {
				ruler.innerHTML = '';
			}
			Array.from(track.querySelectorAll('[data-task-id], [data-planeacion-free-slot], [data-planeacion-timeline-lunch]')).forEach(function (node) {
				node.remove();
			});
			return null;
		}

		var hours = getDayHours(dayHours, dayKey);
		var metrics = getTimelineMetrics(hours.start, hours.end);

		scroll.hidden = false;
		timeline.style.setProperty('--timeline-span', String(metrics.span));

		renderTimelineRuler(ruler, metrics);

		Array.from(track.querySelectorAll('[data-task-id], [data-planeacion-free-slot]')).forEach(function (node) {
			node.remove();
		});

		renderTimelineLunchBand(track, metrics);

		computeTimelineFreeSlots(items, metrics).forEach(function (slot) {
			if (slot.end - slot.start < 15) {
				return;
			}
			track.appendChild(renderTimelineFreeSlot(slot, metrics));
		});

		return metrics;
	}

	function currentMinutesNow() {
		var hour = 0;
		var minute = 0;

		new Intl.DateTimeFormat('en-GB', {
			timeZone: planeacionTimezone(),
			hour: '2-digit',
			minute: '2-digit',
			hour12: false
		}).formatToParts(new Date()).forEach(function (part) {
			if (part.type === 'hour') {
				hour = parseInt(part.value, 10);
			}
			if (part.type === 'minute') {
				minute = parseInt(part.value, 10);
			}
		});

		return (hour * 60) + minute;
	}

	function generateId() {
		return 'task_' + Date.now().toString(36) + Math.random().toString(36).slice(2, 6);
	}

	function weekStartFromDate(dateStr) {
		var parts = String(dateStr || '').split('-');
		if (parts.length !== 3) {
			return bootstrapData().semana_inicio || '';
		}

		var date = new Date(parseInt(parts[0], 10), parseInt(parts[1], 10) - 1, parseInt(parts[2], 10));
		var day = date.getDay();
		var diff = day === 0 ? -6 : 1 - day;
		date.setDate(date.getDate() + diff);

		return date.getFullYear() + '-' +
			String(date.getMonth() + 1).padStart(2, '0') + '-' +
			String(date.getDate()).padStart(2, '0');
	}

	function shiftWeek(weekStart, deltaWeeks) {
		var parts = String(weekStart).split('-');
		var date = new Date(parseInt(parts[0], 10), parseInt(parts[1], 10) - 1, parseInt(parts[2], 10));
		date.setDate(date.getDate() + (deltaWeeks * 7));
		return date.getFullYear() + '-' +
			String(date.getMonth() + 1).padStart(2, '0') + '-' +
			String(date.getDate()).padStart(2, '0');
	}

	function buildDaysForWeekStart(weekStart) {
		var parts = String(weekStart || '').split('-');
		if (parts.length !== 3) {
			return [];
		}

		var start = new Date(parseInt(parts[0], 10), parseInt(parts[1], 10) - 1, parseInt(parts[2], 10));
		var days = [];

		for (var i = 0; i < 7; i++) {
			var date = new Date(start);
			date.setDate(start.getDate() + i);
			days.push({
				index: i,
				label: WEEKDAY_LABELS[i],
				date: date.getFullYear() + '-' +
					String(date.getMonth() + 1).padStart(2, '0') + '-' +
					String(date.getDate()).padStart(2, '0'),
				short: String(date.getDate()).padStart(2, '0') + '/' +
					String(date.getMonth() + 1).padStart(2, '0')
			});
		}

		return days;
	}

	function cloneDayHoursTemplate(dayHours) {
		var clean = {};
		var day;

		for (day = 0; day <= 6; day++) {
			var slot = getDaySlot(dayHours, day);
			clean[String(day)] = {
				start: slot.start,
				end: slot.end,
				day_type: slot.day_type || '',
				day_type_manual: !!slot.day_type_manual
			};
		}

		return clean;
	}

	function buildWeekDayHoursFromTemplate(templateDayHours, weekDays, calendarConfig) {
		var hours = cloneDayHoursTemplate(templateDayHours);
		var day;

		for (day = 0; day <= 6; day++) {
			hours[String(day)].day_type = '';
			hours[String(day)].day_type_manual = false;
		}

		return normalizeDayHours(hours, null, null, weekDays, calendarConfig);
	}

	function inheritDayHoursForWeek(templateDayHours, weekDays, calendarConfig) {
		return buildWeekDayHoursFromTemplate(templateDayHours, weekDays, calendarConfig);
	}

	function buildScheduleWeekContexts(semanaInicio, dayHours, days, calendarConfig, maxWeeks) {
		var contexts = [];
		var limit = Math.max(1, Math.min(maxWeeks || MAX_SCHEDULE_WEEKS, MAX_SCHEDULE_WEEKS));
		var weekStart = semanaInicio || (days[0] && days[0].date ? weekStartFromDate(days[0].date) : '');

		for (var w = 0; w < limit; w++) {
			var ws = w === 0 ? weekStart : shiftWeek(weekStart, w);
			var wDays = w === 0 && days && days.length ? days : buildDaysForWeekStart(ws);
			contexts.push({
				weekOffset: w,
				weekStart: ws,
				days: wDays,
				dayHours: w === 0
					? dayHours
					: buildWeekDayHoursFromTemplate(dayHours, wDays, calendarConfig)
			});
		}

		return contexts;
	}

	function resolveWeekPlanningContext(displayWeekOffset, semanaInicio, dayHours, days, calendarConfig) {
		if (!displayWeekOffset) {
			return {
				dayHours: dayHours,
				days: days
			};
		}

		var weekStart = shiftWeek(semanaInicio, displayWeekOffset);
		var weekDays = buildDaysForWeekStart(weekStart);

		return {
			dayHours: buildWeekDayHoursFromTemplate(dayHours, weekDays, calendarConfig),
			days: weekDays
		};
	}

	function getSegmentDisplayWeekOffset(segment, carryoverShift) {
		return Math.max(0, (segment.week_offset || 0) - (carryoverShift || 0));
	}

	function formatWeekLabel(weekStart, days) {
		if (!days || !days.length) {
			return weekStart;
		}

		var first = days[0];
		var last = days[days.length - 1];
		return (first.short || first.date) + ' ? ' + (last.short || last.date);
	}

	function weekdayIndexYmd(date) {
		if (!date) {
			return 0;
		}

		var parts = String(date).split('-');
		if (parts.length < 3) {
			return 0;
		}

		var dt = new Date(parseInt(parts[0], 10), parseInt(parts[1], 10) - 1, parseInt(parts[2], 10));
		var dow = dt.getDay();

		return dow === 0 ? 6 : dow - 1;
	}

	function isNonWorkingDayType(dayType) {
		return dayType === 'festivo' || dayType === 'libre' || dayType === 'vacaciones' || dayType === 'permisos';
	}

	var PLANEACION_DAY_TYPE_KEYS = ['festivo', 'libre', 'vacaciones', 'permisos'];

	function syncDayTypeSelectClasses(selectEl, dayType, isOff) {
		if (!selectEl) {
			return;
		}
		PLANEACION_DAY_TYPE_KEYS.forEach(function (key) {
			selectEl.classList.remove('logistica-planeacion__day-type--' + key);
		});
		selectEl.classList.toggle('logistica-planeacion__day-type--off', isOff);
		if (isOff && dayType) {
			selectEl.classList.add('logistica-planeacion__day-type--' + dayType);
		}
	}

	function getNonWorkingDayViewConfig(dayType, dayTypes) {
		var labels = dayTypes || {};
		var label = labels[dayType] || 'No laborable';
		var configs = {
			festivo: {
				title: label,
				description: 'Festivo o domingo. Sin actividades programadas en este d?a.',
				icon: 'dashicons-star-filled'
			},
			libre: {
				title: label,
				description: 'D?a libre personal. Sin actividades programadas.',
				icon: 'dashicons-heart'
			},
			vacaciones: {
				title: label,
				description: 'Periodo de vacaciones. Sin actividades programadas.',
				icon: 'dashicons-location-alt'
			},
			permisos: {
				title: label,
				description: 'D?a con permiso o licencia. Sin actividades programadas.',
				icon: 'dashicons-id-alt'
			}
		};

		return configs[dayType] || {
			title: label,
			description: 'D?a no laborable. Sin actividades programadas.',
			icon: 'dashicons-dismiss'
		};
	}

	function getAutomaticDayType(date, calendarConfig) {
		if (!date) {
			return '';
		}

		calendarConfig = calendarConfig || {};
		var settings = calendarConfig.holiday_settings || {};

		if (settings.auto_sundays !== false && weekdayIndexYmd(date) === 6) {
			return 'festivo';
		}

		var festivoDates = calendarConfig.festivo_dates || {};
		if (festivoDates[date]) {
			return 'festivo';
		}

		return '';
	}

	function resolveDayTypeForSlot(date, slot, calendarConfig) {
		slot = slot || {};

		if (slot.day_type_manual) {
			return slot.day_type != null ? String(slot.day_type) : '';
		}

		var auto = getAutomaticDayType(date, calendarConfig);
		if (auto) {
			return auto;
		}

		return slot.day_type != null ? String(slot.day_type) : '';
	}

	function defaultDayHours() {
		var hours = {};
		var day;

		for (day = 0; day <= 6; day++) {
			hours[String(day)] = {
				start: 420,
				end: day === 5 || day === 6 ? 720 : 1020,
				day_type: '',
				day_type_manual: false
			};
		}

		return hours;
	}

	function applyAutomaticDayTypes(dayHours, days, calendarConfig) {
		if (!dayHours || !days || !days.length) {
			return dayHours;
		}

		days.forEach(function (dayMeta) {
			var key = String(dayMeta.index);
			var slot = dayHours[key];
			if (!slot) {
				slot = defaultDayHours()[key];
				dayHours[key] = slot;
			}

			if (slot.day_type_manual) {
				slot.day_type = slot.day_type != null ? String(slot.day_type) : '';
				return;
			}

			var auto = getAutomaticDayType(dayMeta.date, calendarConfig);
			slot.day_type = auto || '';
		});

		return dayHours;
	}

	function normalizeDayHours(raw, fallbackStart, fallbackEnd, days, calendarConfig) {
		var defaults = defaultDayHours();
		var clean = {};
		var day;

		if (raw && typeof raw === 'object') {
			Object.keys(raw).forEach(function (dayKey) {
				var slot = raw[dayKey];
				var dayIndex = parseInt(dayKey, 10);
				if (!slot || dayIndex < 0 || dayIndex > 6) {
					return;
				}
				var start = Number(slot.start);
				var end = Number(slot.end);
				if (!Number.isFinite(start)) {
					start = defaults[String(dayIndex)].start;
				}
				if (!Number.isFinite(end) || end <= start) {
					end = defaults[String(dayIndex)].end;
				}
				clean[String(dayIndex)] = {
					start: start,
					end: end,
					day_type: slot.day_type != null ? String(slot.day_type) : '',
					day_type_manual: !!slot.day_type_manual
				};
			});
		}

		for (day = 0; day <= 6; day++) {
			if (!clean[String(day)]) {
				clean[String(day)] = {
					start: Number.isFinite(fallbackStart) ? fallbackStart : defaults[String(day)].start,
					end: Number.isFinite(fallbackEnd) ? fallbackEnd : defaults[String(day)].end,
					day_type: '',
					day_type_manual: false
				};
			}
		}

		if (days && days.length) {
			applyAutomaticDayTypes(clean, days, calendarConfig);
		}

		return clean;
	}

	function getDaySlot(dayHours, dayKey) {
		var key = String(dayKey);
		var defaults = defaultDayHours()[key] || { start: 420, end: 1020, day_type: '', day_type_manual: false };
		var slot = dayHours && dayHours[key] ? dayHours[key] : defaults;
		var start = Number(slot.start);
		var end = Number(slot.end);

		if (!Number.isFinite(start)) {
			start = defaults.start;
		}
		if (!Number.isFinite(end) || end <= start) {
			end = defaults.end;
		}

		return {
			start: start,
			end: end,
			day_type: slot.day_type != null ? String(slot.day_type) : '',
			day_type_manual: !!slot.day_type_manual
		};
	}

	function getDayHours(dayHours, dayKey) {
		var slot = getDaySlot(dayHours, dayKey);
		return { start: slot.start, end: slot.end };
	}

	function isDayNonWorking(dayKey, dayHours, days, calendarConfig) {
		var date = getScheduledDayDate(dayKey, days);
		var slot = getDaySlot(dayHours, dayKey);

		if (slot.day_type_manual && !slot.day_type) {
			return false;
		}

		var dayType = resolveDayTypeForSlot(date, slot, calendarConfig);
		return isNonWorkingDayType(dayType);
	}

	function findAdjacentWorkingDay(fromDay, direction, dayHours, days, calendarConfig) {
		var step = direction > 0 ? 1 : -1;
		var day = parseInt(fromDay, 10) + step;

		while (day >= 0 && day <= 6) {
			if (!isDayNonWorking(day, dayHours, days, calendarConfig)) {
				return day;
			}
			day += step;
		}

		return null;
	}

	function findNextWorkingDay(fromDay, dayHours, days, calendarConfig) {
		var start = parseInt(fromDay, 10) || 0;
		for (var d = start; d <= 6; d++) {
			if (!isDayNonWorking(d, dayHours, days, calendarConfig)) {
				return d;
			}
		}

		return null;
	}

	function findLastWorkingDay(fromDay, dayHours, days, calendarConfig) {
		var start = parseInt(fromDay, 10);
		if (!Number.isFinite(start)) {
			start = 6;
		}
		for (var d = start; d >= 0; d--) {
			if (!isDayNonWorking(d, dayHours, days, calendarConfig)) {
				return d;
			}
		}

		return null;
	}

	var ADJACENT_PREV_DAY_COUNT = 3;
	var ADJACENT_NEXT_DAY_COUNT = 2;

	function filterDaysForAdjacentStrip(days, stripType) {
		if (!days || !days.length) {
			return [];
		}

		var sorted = days.slice().sort(function (a, b) {
			return (parseInt(a.index, 10) || 0) - (parseInt(b.index, 10) || 0);
		});

		if (stripType === 'prev') {
			var prevStart = Math.max(0, 7 - ADJACENT_PREV_DAY_COUNT);
			return sorted.filter(function (dayMeta) {
				return (parseInt(dayMeta.index, 10) || 0) >= prevStart;
			});
		}

		if (stripType === 'next') {
			return sorted.filter(function (dayMeta) {
				return (parseInt(dayMeta.index, 10) || 0) < ADJACENT_NEXT_DAY_COUNT;
			});
		}

		return [];
	}

	function getAdjacentStripDayRangeLabel(stripType, days) {
		var filtered = filterDaysForAdjacentStrip(days, stripType);
		if (!filtered.length) {
			return stripType === 'prev' ? 'Vie?Dom' : 'Lun?Mar';
		}

		var firstLabel = String(filtered[0].label || filtered[0].short || '').slice(0, 3);
		var lastLabel = String(filtered[filtered.length - 1].label || filtered[filtered.length - 1].short || '').slice(0, 3);
		return firstLabel + '?' + lastLabel;
	}

	function getTaskOrderDay(task) {
		return parseInt(task.day, 10) || 0;
	}

	function compareGlobalTaskOrder(a, b) {
		return (a.sort_order || 0) - (b.sort_order || 0);
	}

	function normalizeGlobalSortOrders(tasks, options) {
		options = options || {};
		if (!tasks || !tasks.length) {
			return tasks || [];
		}

		tasks.sort(function (a, b) {
			if (options.stableDay) {
				var dayA = parseInt(a.day, 10) || 0;
				var dayB = parseInt(b.day, 10) || 0;
				if (dayA !== dayB) {
					return dayA - dayB;
				}
			}
			return (a.sort_order || 0) - (b.sort_order || 0);
		});
		tasks.forEach(function (task, index) {
			task.sort_order = index;
		});
		return tasks;
	}

	function assignSequentialSortOrders(tasks) {
		if (!tasks || !tasks.length) {
			return tasks || [];
		}

		tasks.forEach(function (task, index) {
			task.sort_order = index;
		});
		return tasks;
	}

	function computeGlobalInsertIndex(others, targetDay, targetIndex) {
		var targetDayNum = parseInt(targetDay, 10);
		var sameDayCount = 0;
		var insertAt = others.length;

		for (var i = 0; i < others.length; i++) {
			var dayNum = parseInt(others[i].day, 10);
			if (dayNum > targetDayNum) {
				insertAt = i;
				break;
			}
			if (dayNum === targetDayNum) {
				if (sameDayCount === targetIndex) {
					insertAt = i;
					break;
				}
				sameDayCount += 1;
				insertAt = i + 1;
			}
		}

		return insertAt;
	}

	function repositionTaskInGlobalOrder(tasks, taskId, targetDay) {
		var task = null;
		var others = [];

		tasks.forEach(function (item) {
			if (item.id === taskId) {
				task = item;
			} else {
				others.push(item);
			}
		});

		if (!task) {
			return tasks;
		}

		others.sort(compareGlobalTaskOrder);
		var newDay = parseInt(targetDay, 10);
		task.day = newDay;
		delete task.planned_day;

		var insertAt = 0;
		for (var i = 0; i < others.length; i++) {
			if (parseInt(others[i].day, 10) <= newDay) {
				insertAt = i + 1;
			}
		}

		others.splice(insertAt, 0, task);
		return normalizeGlobalSortOrders(others);
	}

	function migrateSortOrderIfNeeded(tasks) {
		if (!tasks || !tasks.length) {
			return tasks || [];
		}

		return normalizeGlobalSortOrders(tasks.slice());
	}

	function restoreTasksToReopenedDays(tasks, dayHours, days, calendarConfig) {
		tasks.forEach(function (task) {
			if (task.planned_day === undefined || task.planned_day === null) {
				return;
			}

			var plannedDay = parseInt(task.planned_day, 10);
			if (!Number.isFinite(plannedDay)) {
				return;
			}

			if (isDayNonWorking(plannedDay, dayHours, days, calendarConfig)) {
				return;
			}

			task.day = plannedDay;

			if (parseInt(task.day, 10) === plannedDay) {
				delete task.planned_day;
			}
		});
	}

	function releaseTasksFromReopenedDays(tasks, dayHours, days, calendarConfig) {
		restoreTasksToReopenedDays(tasks, dayHours, days, calendarConfig);

		tasks.forEach(function (task) {
			var homeDay = parseInt(task.day, 10);
			if (!Number.isFinite(homeDay)) {
				return;
			}

			if (isDayNonWorking(homeDay, dayHours, days, calendarConfig)) {
				return;
			}

			if (task.planned_day === undefined || task.planned_day === null) {
				return;
			}

			var plannedDay = parseInt(task.planned_day, 10);
			if (!Number.isFinite(plannedDay)) {
				delete task.planned_day;
				return;
			}

			if (!isDayNonWorking(plannedDay, dayHours, days, calendarConfig)) {
				delete task.planned_day;
			}
		});
	}

	function markTasksOnClosedDays(tasks, dayHours, days, calendarConfig) {
		tasks.forEach(function (task) {
			var homeDay = parseInt(task.day, 10) || 0;
			if (!isDayNonWorking(homeDay, dayHours, days, calendarConfig)) {
				return;
			}

			if (task.planned_day === undefined || task.planned_day === null) {
				task.planned_day = homeDay;
			}
		});
	}

	function shiftTasksOffClosedDay(tasks, closedDay, dayHours, days, calendarConfig) {
		closedDay = parseInt(closedDay, 10);
		if (!Number.isFinite(closedDay) || !isDayNonWorking(closedDay, dayHours, days, calendarConfig)) {
			return false;
		}

		var nextWorking = findAdjacentWorkingDay(closedDay, 1, dayHours, days, calendarConfig);
		var moved = false;

		tasks.forEach(function (task) {
			if (parseInt(task.day, 10) !== closedDay) {
				return;
			}

			if (task.planned_day === undefined || task.planned_day === null) {
				task.planned_day = closedDay;
			}
			delete task.planned_start;

			if (nextWorking !== null) {
				task.day = nextWorking;
			}
			moved = true;
		});

		if (moved) {
			tasks.sort(compareGlobalTaskOrder);
			assignSequentialSortOrders(tasks);
		}

		return moved;
	}

	function reconcileTasksWithClosedDays(tasks, dayHours, days, calendarConfig) {
		var changed = false;

		for (var d = 0; d <= 6; d++) {
			if (shiftTasksOffClosedDay(tasks, d, dayHours, days, calendarConfig)) {
				changed = true;
			}
		}

		return changed;
	}

	function getScheduledDayDate(dayIndex, days) {
		if (!days || !days.length) {
			return null;
		}

		var index = parseInt(dayIndex, 10);
		for (var i = 0; i < days.length; i++) {
			if (parseInt(days[i].index, 10) === index) {
				return days[i].date || null;
			}
		}

		return null;
	}

	function getContextDayMeta(ctx, dayKey) {
		if (!ctx || !ctx.days) {
			return null;
		}

		var dayNum = parseInt(dayKey, 10);
		var list = ctx.days;

		if (Array.isArray(list)) {
			for (var i = 0; i < list.length; i++) {
				if (parseInt(list[i].index, 10) === dayNum) {
					return list[i];
				}
			}
			if (list[dayNum]) {
				return list[dayNum];
			}
		} else if (list[String(dayNum)]) {
			return list[String(dayNum)];
		}

		return null;
	}

	function todayDateKey() {
		try {
			return new Intl.DateTimeFormat('en-CA', {
				timeZone: planeacionTimezone(),
				year: 'numeric',
				month: '2-digit',
				day: '2-digit'
			}).format(new Date());
		} catch (error) {
		var today = new Date();
		return today.getFullYear() + '-' +
			String(today.getMonth() + 1).padStart(2, '0') + '-' +
			String(today.getDate()).padStart(2, '0');
		}
	}

	function compareDayToToday(isoDate) {
		if (!isoDate) {
			return null;
		}

		var todayKey = todayDateKey();
		if (isoDate < todayKey) {
			return -1;
		}
		if (isoDate > todayKey) {
			return 1;
		}
		return 0;
	}

	function hasRescheduleDelay(task) {
		if (task.reschedule_delay) {
			return true;
		}

		var homeDay = parseInt(task.day, 10) || 0;
		var plannedDay = task.planned_day !== undefined && task.planned_day !== null
			? parseInt(task.planned_day, 10)
			: null;
		var startedDay = task.started_on_day !== undefined && task.started_on_day !== null
			? parseInt(task.started_on_day, 10)
			: null;

		if (plannedDay === null || homeDay <= plannedDay) {
			return false;
		}

		return task.status === 'in_progress' || startedDay !== null;
	}

	function resolveTaskStatus(task, finish, now, days) {
		var status = task.status || 'pending';

		if (status === 'done') {
			return 'done';
		}

		var homeDay = parseInt(task.day, 10) || 0;
		var homeDayCompare = compareDayToToday(getScheduledDayDate(homeDay, days));

		if (hasRescheduleDelay(task)) {
			return 'delayed';
		}

		if (task.status_manual) {
			if (status === 'delayed' && (homeDayCompare === null || homeDayCompare > 0)) {
				return status === 'in_progress' ? 'in_progress' : 'pending';
			}
			if (status === 'in_progress' && homeDayCompare === 0 && now > finish) {
				return 'delayed';
			}
			return status;
		}

		if (homeDayCompare === null || homeDayCompare > 0) {
			if (status === 'delayed') {
				return 'pending';
			}
			return status === 'in_progress' ? 'in_progress' : 'pending';
		}

		if (homeDayCompare < 0) {
			return 'delayed';
		}

		if (status === 'in_progress') {
			return now > finish ? 'delayed' : 'in_progress';
		}

		return 'pending';
	}

	function applyDayChangeEffects(task, oldTask, newDay, oldDay) {
		newDay = parseInt(newDay, 10);
		oldDay = parseInt(oldDay, 10);
		if (!Number.isFinite(newDay) || !Number.isFinite(oldDay) || newDay === oldDay) {
				return;
			}

		task.planned_day = oldTask.planned_day !== undefined && oldTask.planned_day !== null
			? parseInt(oldTask.planned_day, 10)
			: oldDay;
		task.started_on_day = oldTask.started_on_day;

		if (newDay > oldDay && (oldTask.status === 'in_progress' || (oldTask.started_on_day !== undefined && oldTask.started_on_day !== null))) {
					task.status = 'delayed';
			task.status_manual = false;
			task.reschedule_delay = true;
			return;
		}

		if (newDay < oldDay) {
			task.reschedule_delay = false;
		}
	}

	function getPlaneacionPlanner() {
		if (!window.LogisticaPlaneacionPlanner) {
			return null;
		}
		return window.LogisticaPlaneacionPlanner.create({
			LUNCH_START: LUNCH_START,
			LUNCH_END: LUNCH_END,
			MAX_SCHEDULE_WEEKS: MAX_SCHEDULE_WEEKS,
			normalizeDayHours: normalizeDayHours,
			markTasksOnClosedDays: markTasksOnClosedDays,
			buildScheduleWeekContexts: buildScheduleWeekContexts,
			isDayNonWorking: isDayNonWorking,
			findAdjacentWorkingDay: findAdjacentWorkingDay,
			getDayHours: getDayHours,
			currentMinutesNow: currentMinutesNow,
			compareGlobalTaskOrder: compareGlobalTaskOrder,
			applyDurationToTask: applyDurationToTask,
			skipLunch: skipLunch,
			capFinishBeforeLunch: capFinishBeforeLunch,
			advanceCursor: advanceCursor,
			resolveTaskStatus: resolveTaskStatus,
			placeFallbackTaskSegment: placeFallbackTaskSegment,
			getSegmentDisplayWeekOffset: getSegmentDisplayWeekOffset
		});
	}

	function taskSchedulingKey(task) {
		var api = getPlaneacionPlanner();
		return api ? api.taskSchedulingKey(task) : ('id:' + (task && task.id));
	}

	function normalizeSegmentForDisplayWeek(seg, currentDays) {
		var api = getPlaneacionPlanner();
		return api ? api.normalizeSegmentForDisplayWeek(seg, currentDays) : seg;
	}

	function getWeekDateSet(days) {
		var api = getPlaneacionPlanner();
		if (api) {
			return api.getWeekDateSet(days);
		}
		var set = {};
		(days || []).forEach(function (dayMeta) {
			if (dayMeta.date) {
				set[dayMeta.date] = true;
			}
		});
		return set;
	}

	function segmentMatchesColumnDay(seg, dayNum, columnDate, displayWeekOffset, carryoverShift) {
		var api = getPlaneacionPlanner();
		if (api) {
			return api.segmentMatchesColumnDay(seg, dayNum, columnDate, displayWeekOffset, carryoverShift);
		}
		return parseInt(seg.day, 10) === parseInt(dayNum, 10);
	}

	function recalculateTasks(tasks, dayHours, days, calendarConfig, semanaInicio, options) {
		var api = getPlaneacionPlanner();
		if (!api) {
			if (options && options.returnCarryover) {
				return { tasks: tasks || [], carryoverTasks: [] };
			}
			return tasks || [];
		}
		return api.recalculateTasks(tasks, dayHours, days, calendarConfig, semanaInicio, options || {});
	}

	function placeFallbackTaskSegment(task, dayHours, days, calendarConfig, semanaInicio, weekContexts) {
		if (!weekContexts || !weekContexts.length) {
			weekContexts = buildScheduleWeekContexts(
				semanaInicio,
				dayHours,
				days,
				calendarConfig,
				MAX_SCHEDULE_WEEKS
			);
		}

		var segmentWeekOffset = 0;
		var segmentDay = 0;
		var found = false;

		for (var w = 0; w < weekContexts.length; w++) {
			var startDay = 0;
			for (var d = startDay; d <= 6; d++) {
				var ctx = weekContexts[w];
				if (isDayNonWorking(d, ctx.dayHours, ctx.days, calendarConfig)) {
					continue;
				}

				segmentWeekOffset = w;
				segmentDay = d;
				found = true;
						break;
			}

			if (found) {
				break;
			}
		}

		var activeCtx = weekContexts[segmentWeekOffset] || weekContexts[0];
		var daySlot = getDayHours(activeCtx.dayHours, segmentDay);
		var dayMeta = getContextDayMeta(activeCtx, segmentDay);
				var duration = Number(task.duration_minutes);
				if (!Number.isFinite(duration) || duration < 15) {
					duration = 60;
					task.duration_minutes = duration;
				}

				var start = daySlot.start;
				task.schedule_segments = [{
			day: segmentDay,
			week_offset: segmentWeekOffset,
			date: dayMeta ? dayMeta.date : '',
					start_minutes: start,
					end_minutes: start + duration,
					minutes: duration,
					is_continuation: false
				}];
				task.start_minutes = start;
				task.end_minutes = start + duration;
	}

	function resolveStoredMachine(machines, fallback) {
		var slugs = Object.keys(machines || {});
		if (!slugs.length) {
			return fallback || 'sm74';
		}

		try {
			var stored = window.localStorage.getItem('logistica_planeacion_machine');
			if (stored && machines[stored]) {
				return stored;
			}
		} catch (error) {
			// ignore
		}

		if (fallback && machines[fallback]) {
			return fallback;
		}

		return slugs[0];
	}

	function resolvePlaneacionScope(bootstrapId, initFlag) {
		var bootstrapNode = document.getElementById(bootstrapId);
		if (!bootstrapNode || bootstrapNode.getAttribute(initFlag) === '1') {
			return null;
		}

		var root = bootstrapNode.closest('[data-tab-panel]') || bootstrapNode.parentElement;
		if (!root) {
			return null;
		}

		var board = root.querySelector('[data-planeacion-board]');
		if (!board) {
			return null;
		}

		bootstrapNode.setAttribute(initFlag, '1');

		return {
			bootstrapNode: bootstrapNode,
			root: root,
			board: board
		};
	}

	function parseAjaxResponse(text) {
		var trimmed = String(text || '').trim();

		if (!trimmed) {
			throw new Error('empty_response');
		}

		if (trimmed === '-1') {
			return {
				success: false,
				data: {
					message: 'Sesi?n expirada o solicitud no v?lida. Recarga la p?gina e int?ntalo de nuevo.'
				}
			};
		}

		if (trimmed === '0') {
			return {
				success: false,
				data: {
					message: 'Acci?n no disponible en el servidor.'
				}
			};
		}

		return JSON.parse(trimmed);
	}

	function initPlaneacionAdminSettings(scope, settings) {
		settings = settings || {};
		var board = scope.board;
		var root = scope.root;
		if (board.getAttribute('data-planeacion-admin-settings-bound') === '1') {
			return;
		}
		board.setAttribute('data-planeacion-admin-settings-bound', '1');

		var bootstrap = bootstrapData();
		var config = adminConfig();
		var saveAction = settings.saveAction || '';
		var nonce = settings.nonce || '';
		var saving = false;
		var state = {
			fieldOptions: loadFieldOptionsConfig(bootstrap)
		};
		var optionsModal = root.querySelector('[data-planeacion-options-modal]');
		var optionsForm = root.querySelector('[data-planeacion-options-form]');
		var saveStatusNode = board.querySelector('[data-planeacion-save-status]');

		function bindActionClicks(container, handlers) {
			if (!container) {
				return;
			}

			container.addEventListener('click', function (event) {
				var actionNode = event.target.closest('[data-action]');
				if (!actionNode || !container.contains(actionNode)) {
					return;
				}

				var action = actionNode.getAttribute('data-action');
				if (!handlers[action]) {
					return;
				}

				event.preventDefault();
				handlers[action](event, actionNode);
			});
		}

		function checklistToTextarea(list) {
			return Array.isArray(list) ? list.join('\n') : '';
		}

		function materialsToTextarea(materials) {
			if (!materials || typeof materials !== 'object') {
				return '';
			}

			return Object.keys(materials).map(function (key) {
				return materials[key];
			}).join('\n');
		}

		function setSaveStatus(text, type) {
			if (!saveStatusNode) {
				return;
			}

			saveStatusNode.textContent = text || '';
			saveStatusNode.classList.remove('is-saving', 'is-error');
			if (type) {
				saveStatusNode.classList.add(type);
			}
		}

		function ajaxRequest(action, data) {
			var body = new FormData();
			body.append('action', action);
			body.append('nonce', nonce);

			Object.keys(data || {}).forEach(function (key) {
				body.append(key, data[key]);
			});

			return fetch(config.ajaxUrl || '/wp-admin/admin-ajax.php', {
				method: 'POST',
				credentials: 'same-origin',
				body: body
			}).then(function (response) {
				return response.text().then(function (text) {
					return parseAjaxResponse(text);
				});
			});
		}

		function fillOptionsForm() {
			if (!optionsForm) {
				return;
			}

			optionsForm.querySelectorAll('[data-planeacion-options-field]').forEach(function (field) {
				var key = field.getAttribute('data-planeacion-options-field');
				if (key === 'papel_materials') {
					field.value = materialsToTextarea(state.fieldOptions.papel_materials);
					return;
				}
				field.value = checklistToTextarea(state.fieldOptions[key]);
			});
		}

		function openOptionsModal() {
			if (!optionsModal || !optionsForm) {
				return;
			}

			fillOptionsForm();

			if (typeof optionsModal.showModal === 'function') {
				optionsModal.showModal();
			} else {
				optionsModal.setAttribute('open', 'open');
			}
		}

		function closeOptionsModal() {
			if (!optionsModal) {
				return;
			}

			if (typeof optionsModal.close === 'function') {
				optionsModal.close();
			} else {
				optionsModal.removeAttribute('open');
			}
		}

		function saveFieldOptions() {
			if (saving || !optionsForm || !saveAction) {
				return;
			}

			var payload = {};
			optionsForm.querySelectorAll('[data-planeacion-options-field]').forEach(function (field) {
				payload[field.getAttribute('data-planeacion-options-field')] = String(field.value || '');
			});

			saving = true;
			setSaveStatus('Guardando listas?', 'is-saving');

			return ajaxRequest(saveAction, {
				options_json: JSON.stringify(payload)
			}).then(function (response) {
				if (!response || !response.success) {
					setSaveStatus((response && response.data && response.data.message) || 'No se pudieron guardar las listas', 'is-error');
					return;
				}

				if (response.data.field_options) {
					state.fieldOptions = loadFieldOptionsConfig({ field_options: response.data.field_options });
				}

				closeOptionsModal();
				setSaveStatus(response.data.message || 'Listas desplegables guardadas');
			}).catch(function () {
				setSaveStatus('Error de conexi?n', 'is-error');
			}).finally(function () {
				saving = false;
			});
		}

		bindActionClicks(board, {
			'planeacion-edit-field-options': function () {
				openOptionsModal();
			}
		});

		bindActionClicks(optionsModal, {
			'planeacion-close-options-modal': function () {
				closeOptionsModal();
			},
			'planeacion-save-field-options': function () {
				saveFieldOptions();
			}
		});

		if (optionsModal) {
			optionsModal.addEventListener('cancel', function (event) {
				event.preventDefault();
				closeOptionsModal();
			});
		}

		if (optionsForm) {
			optionsForm.addEventListener('submit', function (event) {
				event.preventDefault();
				saveFieldOptions();
			});
		}
	}

	function initPlaneacionImpresion() {
		var scope = resolvePlaneacionScope('logistica-planeacion-bootstrap', 'data-planeacion-impresion-init');
		if (!scope) {
			return;
		}

		var board = scope.board;
		var root = scope.root;

		if (board.getAttribute('data-planeacion-admin-settings-only') === '1') {
			initPlaneacionAdminSettings(scope, {
				saveAction: 'logistica_save_planeacion_field_options',
				nonce: adminConfig().planeacionImpresionNonce || ''
			});
			return;
		}

		bindTintasPanelEvents();

		var zoomRoot = board.querySelector('[data-excel-zoom-root="planeacion-impresion"]');
		if (zoomRoot && window.LogisticaExcelZoom && typeof window.LogisticaExcelZoom.init === 'function') {
			window.LogisticaExcelZoom.init(zoomRoot);
		}

		var bootstrap = bootstrapData();
		loadLaborConfig(bootstrap);
		var config = adminConfig();
		var calendarConfig = {
			day_types: bootstrap.day_types || {},
			holiday_settings: bootstrap.holiday_settings || {},
			festivo_dates: bootstrap.festivo_dates || {}
		};
		var state = {
			semana_inicio: bootstrap.semana_inicio || weekStartFromDate(''),
			machines: bootstrap.machines || {},
			customMachineSlugs: bootstrap.custom_machine_slugs || [],
			fieldOptions: loadFieldOptionsConfig(bootstrap),
			machine: resolveStoredMachine(bootstrap.machines, 'sm74'),
			calendarConfig: calendarConfig,
			day_hours: normalizeDayHours(bootstrap.day_hours, null, null, bootstrap.days || [], calendarConfig),
			tasks: [],
			days: bootstrap.days || [],
			statuses: bootstrap.statuses || {},
			include_lunch: true,
			lunch_start: 720,
			lunch_end: 780,
			laborTypes: bootstrap.labor_types || LABOR_TYPES,
			saveTimer: null,
			loading: false,
			editingId: null,
			modalViewOnly: false,
			complexities: bootstrap.complexities || {},
			dragId: null,
			carryoverTasks: [],
			prevWeekSchedule: null,
			prevWeekDays: [],
			prevSemanaInicio: '',
			nextWeekSchedule: null,
			nextWeekDays: [],
			nextSemanaInicio: '',
			previewTasksPrev: [],
			previewTasksNext: [],
			editingWeekOffset: 0,
			adjacentStripExpanded: { prev: false, next: false }
		};

		function getPriorWeekBundle() {
			if (!state.prevWeekSchedule || !Array.isArray(state.prevWeekSchedule.tasks) || !state.prevWeekSchedule.tasks.length) {
				return null;
			}

			return {
				tasks: state.prevWeekSchedule.tasks.slice(),
				day_hours: state.prevWeekSchedule.day_hours,
				days: state.prevWeekDays || [],
				semana_inicio: state.prevSemanaInicio,
				jornada_inicio: state.prevWeekSchedule.jornada_inicio,
				jornada_fin: state.prevWeekSchedule.jornada_fin
			};
		}

		function getWeekContextByOffset(weekOffset) {
			weekOffset = parseInt(weekOffset, 10) || 0;

			if (weekOffset === 0) {
				return {
					weekOffset: 0,
					semanaInicio: state.semana_inicio,
					days: state.days,
					dayHours: state.day_hours,
					schedule: null
				};
			}

			if (weekOffset < 0) {
				return {
					weekOffset: weekOffset,
					semanaInicio: state.prevSemanaInicio,
					days: state.prevWeekDays || [],
					dayHours: state.prevWeekSchedule
						? normalizeScheduleDayHours(
							state.prevWeekSchedule.day_hours,
							parseInt(state.prevWeekSchedule.jornada_inicio, 10) || 420,
							parseInt(state.prevWeekSchedule.jornada_fin, 10) || 1020
						)
						: state.day_hours,
					schedule: state.prevWeekSchedule
				};
			}

			return {
				weekOffset: weekOffset,
				semanaInicio: state.nextSemanaInicio,
				days: state.nextWeekDays || [],
				dayHours: state.nextWeekSchedule
					? normalizeScheduleDayHours(
						state.nextWeekSchedule.day_hours,
						parseInt(state.nextWeekSchedule.jornada_inicio, 10) || 420,
						parseInt(state.nextWeekSchedule.jornada_fin, 10) || 1020
					)
					: state.day_hours,
				schedule: state.nextWeekSchedule
			};
		}

		function getWeekScheduleBundle(weekOffset) {
			weekOffset = parseInt(weekOffset, 10) || 0;
			var ctx = getWeekContextByOffset(weekOffset);

			if (weekOffset === 0) {
				return {
					semanaInicio: state.semana_inicio,
					tasks: state.tasks.slice(),
					dayHours: state.day_hours,
					days: state.days,
					includeLunch: state.include_lunch,
					lunchStart: state.lunch_start,
					lunchEnd: state.lunch_end,
					isCurrent: true,
					weekOffset: 0,
					scheduleRef: null
				};
			}

			if (!ctx.schedule || !ctx.semanaInicio) {
				return null;
			}

			var scheduleLunch = syncLunchTimesToModule(ctx.schedule.lunch_start, ctx.schedule.lunch_end);

			return {
				semanaInicio: ctx.semanaInicio,
				tasks: (ctx.schedule.tasks || []).slice(),
				dayHours: ctx.dayHours,
				days: ctx.days,
				includeLunch: normalizeIncludeLunch(ctx.schedule.include_lunch),
				lunchStart: scheduleLunch.lunch_start,
				lunchEnd: scheduleLunch.lunch_end,
				isCurrent: false,
				weekOffset: weekOffset,
				scheduleRef: ctx.schedule
			};
		}

		function buildPayloadFromParts(dayHours, tasks, includeLunch, lunchStart, lunchEnd) {
			var monday = getDayHours(dayHours, 0);
			var lunch = syncLunchTimesToModule(lunchStart, lunchEnd);
			return {
				jornada_inicio: monday.start,
				jornada_fin: monday.end,
				day_hours: dayHours,
				include_lunch: includeLunch ? 1 : 0,
				lunch_start: lunch.lunch_start,
				lunch_end: lunch.lunch_end,
				tasks: tasks.map(stripComputedTaskFields)
			};
		}

		function saveWeekScheduleDirect(semanaInicio, payload) {
			return ajax('logistica_save_planeacion_impresion', {
				semana_inicio: semanaInicio,
				maquina: state.machine,
				schedule_json: JSON.stringify(payload)
			});
		}

		function refreshAdjacentWeekPreviews() {
			state.previewTasksPrev = [];
			state.previewTasksNext = [];

			if (state.prevWeekSchedule && state.prevWeekDays && state.prevWeekDays.length) {
				var prevHours = normalizeScheduleDayHours(
					state.prevWeekSchedule.day_hours,
					parseInt(state.prevWeekSchedule.jornada_inicio, 10) || 420,
					parseInt(state.prevWeekSchedule.jornada_fin, 10) || 1020
				);
				var prevOut = recalculateTasks(
					(state.prevWeekSchedule.tasks || []).slice(),
					prevHours,
					state.prevWeekDays,
					state.calendarConfig,
					state.prevSemanaInicio,
					{}
				);
				state.previewTasksPrev = prevOut.tasks || prevOut || [];
			}

			if (state.nextWeekSchedule && state.nextWeekDays && state.nextWeekDays.length) {
				var nextHours = normalizeScheduleDayHours(
					state.nextWeekSchedule.day_hours,
					parseInt(state.nextWeekSchedule.jornada_inicio, 10) || 420,
					parseInt(state.nextWeekSchedule.jornada_fin, 10) || 1020
				);
				var nextOut = recalculateTasks(
					(state.nextWeekSchedule.tasks || []).slice(),
					nextHours,
					state.nextWeekDays,
					state.calendarConfig,
					state.nextSemanaInicio,
					{}
				);
				state.previewTasksNext = nextOut.tasks || nextOut || [];
			}
		}

		function getTaskSourcesForWeekOffset(weekOffset) {
			weekOffset = parseInt(weekOffset, 10) || 0;

			if (weekOffset === 0) {
				return state.tasks;
			}

			if (weekOffset < 0) {
				return state.previewTasksPrev || [];
			}

			var owned = state.previewTasksNext || [];
			if (weekOffset === 1) {
				return state.tasks.concat(owned.filter(function (task) {
					return !state.tasks.some(function (live) {
						return taskIdMatches(live, task.id);
					});
				}));
			}

			return state.tasks;
		}

		function resolveTaskSourceWeekOffset(taskId) {
			if (state.tasks.some(function (task) {
				return taskIdMatches(task, taskId);
			})) {
				return 0;
			}

			if ((state.carryoverTasks || []).some(function (entry) {
				return entry && entry.task && taskIdMatches(entry.task, taskId);
			})) {
				return -1;
			}

			if ((state.previewTasksPrev || []).some(function (task) {
				return taskIdMatches(task, taskId);
			})) {
				return -1;
			}

			if ((state.previewTasksNext || []).some(function (task) {
				return taskIdMatches(task, taskId);
			})) {
				return 1;
			}

			return 0;
		}

		function findTaskForWeekOffset(taskId, weekOffset) {
			weekOffset = parseInt(weekOffset, 10) || 0;
			var sources = getTaskSourcesForWeekOffset(weekOffset);
			for (var i = 0; i < sources.length; i++) {
				if (taskIdMatches(sources[i], taskId)) {
					return sources[i];
				}
			}

			var resolved = resolveTaskById(taskId);
			return resolved ? resolved.task : null;
		}

		function prepareTaskForWeekPlacement(task, targetDay, plannedStart) {
			var clean = stripComputedTaskFields(Object.assign({}, task));
			clean.day = parseInt(targetDay, 10);
			delete clean.planned_day;
			if (plannedStart != null && Number.isFinite(Number(plannedStart))) {
				clean.planned_start = plannedStart;
			} else {
				delete clean.planned_start;
			}
			return clean;
		}

		function insertTaskIntoWeekTasks(tasks, task, targetDay, insertIndex) {
			var others = tasks.filter(function (item) {
				return !taskIdMatches(item, task.id);
			}).sort(compareGlobalTaskOrder);
			others.splice(computeGlobalInsertIndex(others, targetDay, insertIndex), 0, task);
			return assignSequentialSortOrders(others);
		}

		function persistWeekBundle(bundle) {
			var payload = buildPayloadFromParts(
				bundle.dayHours,
				bundle.tasks,
				bundle.includeLunch,
				bundle.lunchStart,
				bundle.lunchEnd
			);
			return saveWeekScheduleDirect(bundle.semanaInicio, payload).then(function (response) {
				if (!response || !response.success) {
					return response;
				}

				if (bundle.isCurrent) {
					state.tasks = bundle.tasks;
				} else if (bundle.weekOffset < 0) {
					state.prevWeekSchedule = Object.assign({}, bundle.scheduleRef, { tasks: bundle.tasks });
				} else {
					state.nextWeekSchedule = Object.assign({}, bundle.scheduleRef, { tasks: bundle.tasks });
				}

				refreshAdjacentWeekPreviews();
				return response;
			});
		}

		function adoptCarryoverIntoCurrent(task, targetDay, insertIndex, plannedStart) {
			var clean = prepareTaskForWeekPlacement(task, targetDay, plannedStart);
			state.tasks = insertTaskIntoWeekTasks(state.tasks, clean, targetDay, insertIndex);
			removeCarryoverTaskById(task.id);
			renderBoard();
			scheduleSave();
			return Promise.resolve(true);
		}

		function transferTaskBetweenWeeks(taskId, sourceWeekOffset, targetWeekOffset, targetDay, insertIndex, plannedStart) {
			sourceWeekOffset = parseInt(sourceWeekOffset, 10) || 0;
			targetWeekOffset = parseInt(targetWeekOffset, 10) || 0;

			if (sourceWeekOffset === -1 && targetWeekOffset === 0) {
				var carryoverResolved = resolveTaskById(taskId);
				if (carryoverResolved && carryoverResolved.source === 'carryover') {
					return adoptCarryoverIntoCurrent(
						carryoverResolved.task,
						targetDay,
						insertIndex,
						plannedStart
					);
				}
			}

			if (sourceWeekOffset === targetWeekOffset) {
				var sameBundle = getWeekScheduleBundle(sourceWeekOffset);
				if (!sameBundle) {
					return Promise.resolve(false);
				}

				var sameIndex = sameBundle.tasks.findIndex(function (task) {
					return taskIdMatches(task, taskId);
				});
				if (sameIndex < 0) {
					return Promise.resolve(false);
				}

				var sameTask = prepareTaskForWeekPlacement(
					sameBundle.tasks[sameIndex],
					targetDay,
					plannedStart
				);
				var sameOldDay = parseInt(sameBundle.tasks[sameIndex].day, 10);
				applyDayChangeEffects(sameTask, sameBundle.tasks[sameIndex], targetDay, sameOldDay);
				sameBundle.tasks.splice(sameIndex, 1);
				sameBundle.tasks = insertTaskIntoWeekTasks(sameBundle.tasks, sameTask, targetDay, insertIndex);

				return persistWeekBundle(sameBundle).then(function (response) {
					if (response && response.success) {
						renderBoard();
						if (sameBundle.isCurrent) {
							scheduleSave();
						}
					}
					return !!(response && response.success);
				});
			}

			var sourceBundle = getWeekScheduleBundle(sourceWeekOffset);
			var targetBundle = getWeekScheduleBundle(targetWeekOffset);
			if (!sourceBundle || !targetBundle) {
				return Promise.resolve(false);
			}

			var taskIndex = sourceBundle.tasks.findIndex(function (task) {
				return taskIdMatches(task, taskId);
			});
			if (taskIndex < 0) {
				return Promise.resolve(false);
			}

			var movedTask = prepareTaskForWeekPlacement(
				sourceBundle.tasks[taskIndex],
				targetDay,
				plannedStart
			);
			var oldDay = parseInt(sourceBundle.tasks[taskIndex].day, 10);
			applyDayChangeEffects(movedTask, sourceBundle.tasks[taskIndex], targetDay, oldDay);

			sourceBundle.tasks.splice(taskIndex, 1);
			assignSequentialSortOrders(sourceBundle.tasks);
			targetBundle.tasks = insertTaskIntoWeekTasks(targetBundle.tasks, movedTask, targetDay, insertIndex);

			return persistWeekBundle(sourceBundle).then(function (sourceResponse) {
				if (!sourceResponse || !sourceResponse.success) {
					return false;
				}
				return persistWeekBundle(targetBundle).then(function (targetResponse) {
					if (!targetResponse || !targetResponse.success) {
						return false;
					}
					removeCarryoverTaskById(taskId);
					renderBoard();
					if (targetBundle.isCurrent) {
						scheduleSave();
					} else if (sourceBundle.isCurrent) {
						setSaveStatus('Actividad movida a otra semana');
					}
					return true;
				});
			});
		}

		function findCrossWeekBoundaryDay(fromDay, direction, fromWeekOffset) {
			fromWeekOffset = parseInt(fromWeekOffset, 10) || 0;
			var ctx = getWeekContextByOffset(fromWeekOffset);
			var adjacentDay = findAdjacentWorkingDay(
				fromDay,
				direction,
				ctx.dayHours,
				ctx.days,
				state.calendarConfig
			);

			if (adjacentDay !== null) {
				return {
					weekOffset: fromWeekOffset,
					day: adjacentDay,
					crossWeek: false
				};
			}

			var targetWeekOffset = fromWeekOffset + (direction > 0 ? 1 : -1);
			if (targetWeekOffset < -1 || targetWeekOffset > 1) {
				return null;
			}

			var targetCtx = getWeekContextByOffset(targetWeekOffset);
			if (!targetCtx.days || !targetCtx.days.length) {
				return null;
			}

			var targetDay = direction > 0
				? findNextWorkingDay(0, targetCtx.dayHours, targetCtx.days, state.calendarConfig)
				: findLastWorkingDay(6, targetCtx.dayHours, targetCtx.days, state.calendarConfig);

			if (targetDay === null) {
				return null;
			}

			return {
				weekOffset: targetWeekOffset,
				day: targetDay,
				crossWeek: true
			};
		}

		function getZoneWeekOffset(zone) {
			if (!zone) {
				return 0;
			}
			return parseInt(zone.getAttribute('data-planeacion-week-offset') || '0', 10) || 0;
		}

		function getZonePlanningContext(zone) {
			var weekOffset = getZoneWeekOffset(zone);
			var ctx = getWeekContextByOffset(weekOffset);
			return {
				weekOffset: weekOffset,
				dayHours: ctx.dayHours,
				days: ctx.days
			};
		}

		function saveTaskToWeekSchedule(task, weekOffset) {
			weekOffset = parseInt(weekOffset, 10) || 0;
			var bundle = getWeekScheduleBundle(weekOffset);
			if (!bundle) {
				return Promise.resolve(false);
			}

			var taskIndex = bundle.tasks.findIndex(function (item) {
				return taskIdMatches(item, task.id);
			});
			if (taskIndex < 0) {
				return Promise.resolve(false);
			}

			var oldTask = bundle.tasks[taskIndex];
			var newDay = parseInt(task.day, 10);
			var oldDay = parseInt(oldTask.day, 10);
			applyDayChangeEffects(task, oldTask, newDay, oldDay);
			bundle.tasks[taskIndex] = Object.assign({}, oldTask, stripComputedTaskFields(task));

			return persistWeekBundle(bundle).then(function (response) {
				if (response && response.success) {
					renderBoard();
				}
				return !!(response && response.success);
			});
		}

		function createTaskInWeekSchedule(task, weekOffset) {
			weekOffset = parseInt(weekOffset, 10) || 0;
			var bundle = getWeekScheduleBundle(weekOffset);
			if (!bundle) {
				return Promise.resolve(false);
			}

			var clean = stripComputedTaskFields(task);
			clean.planned_day = parseInt(clean.day, 10);
			bundle.tasks = insertTaskIntoWeekTasks(bundle.tasks, clean, clean.day, bundle.tasks.length);

			return persistWeekBundle(bundle).then(function (response) {
				if (response && response.success) {
					renderBoard();
				}
				return !!(response && response.success);
			});
		}

		function deleteTaskFromWeekSchedule(taskId, weekOffset) {
			weekOffset = parseInt(weekOffset, 10) || 0;
			var bundle = getWeekScheduleBundle(weekOffset);
			if (!bundle) {
				return Promise.resolve(false);
			}

			bundle.tasks = bundle.tasks.filter(function (task) {
				return !taskIdMatches(task, taskId);
			});
			assignSequentialSortOrders(bundle.tasks);

			return persistWeekBundle(bundle).then(function (response) {
				if (response && response.success) {
					renderBoard();
				}
				return !!(response && response.success);
			});
		}

		function runScheduleRecalculate() {
			var taskRefs = state.tasks.slice();
			var output = recalculateTasks(
				taskRefs,
				state.day_hours,
				state.days,
				state.calendarConfig,
				state.semana_inicio,
				{
					priorWeek: getPriorWeekBundle(),
					returnCarryover: true
				}
			);

			if (output && output.tasks) {
				state.tasks = output.tasks;
				state.carryoverTasks = output.carryoverTasks || [];
				return;
			}

			state.tasks = output || [];
			state.carryoverTasks = [];
		}

		function normalizeScheduleDayHours(raw, fallbackStart, fallbackEnd) {
			return normalizeDayHours(raw, fallbackStart, fallbackEnd, state.days, state.calendarConfig);
		}

		var weekLabel = board.querySelector('[data-planeacion-week-label]');
		var saveStatus = board.querySelector('[data-planeacion-save-status]');
		var includeLunchCheckbox = board.querySelector('[data-planeacion-include-lunch]');
		var lunchStartInput = board.querySelector('[data-planeacion-lunch-start]');
		var lunchEndInput = board.querySelector('[data-planeacion-lunch-end]');
		var machinesNav = board.querySelector('[data-planeacion-machines]');
		var modal = root.querySelector('[data-planeacion-modal]');
		var form = root.querySelector('[data-planeacion-form]');
		var machineModal = root.querySelector('[data-planeacion-machine-modal]');
		var machineForm = root.querySelector('[data-planeacion-machine-form]');
		var machineModalTitle = machineModal ? machineModal.querySelector('[data-planeacion-machine-modal-title]') : null;
		var machineModalHint = machineModal ? machineModal.querySelector('[data-planeacion-machine-modal-hint]') : null;
		var machineSlugInput = machineForm ? machineForm.querySelector('[data-planeacion-machine-slug]') : null;
		var machineDeleteBtn = machineForm ? machineForm.querySelector('[data-action="planeacion-delete-machine"]') : null;
		var machineSubmitBtn = machineForm ? machineForm.querySelector('[data-planeacion-machine-submit]') : null;
		var optionsModal = root.querySelector('[data-planeacion-options-modal]');
		var optionsForm = root.querySelector('[data-planeacion-options-form]');
		var metricsWeekPanel = root.querySelector('[data-planeacion-metrics-week]');
		var metricsRoot = root.querySelector('[data-planeacion-metrics]');
		var compareMetricsCacheKey = '';
		var pendingCompareMetrics = null;

		function bindModalActions(root, handlers) {
			if (!root) {
				return;
			}

			root.addEventListener('click', function (event) {
				var actionNode = event.target.closest('[data-action]');
				if (!actionNode || !root.contains(actionNode)) {
					return;
				}

				var action = actionNode.getAttribute('data-action');
				if (!handlers[action]) {
					return;
				}

				event.preventDefault();
				handlers[action](event, actionNode);
			});
		}

		function materialsToTextarea(materials) {
			if (!materials || typeof materials !== 'object') {
				return '';
			}

			return Object.keys(materials).map(function (key) {
				return materials[key];
			}).join('\n');
		}

		function checklistToTextarea(list) {
			return Array.isArray(list) ? list.join('\n') : '';
		}

		function syncFormDaySelect() {
			if (!form) {
				return;
			}

			var select = form.querySelector('[data-planeacion-field="day"]');
			if (!select) {
				return;
			}

			var weekOffset = parseInt(state.editingWeekOffset, 10) || 0;
			var ctx = getWeekContextByOffset(weekOffset);
			var days = ctx.days && ctx.days.length ? ctx.days : (state.days || []);
			var current = select.value;
			var html = '';

			days.forEach(function (dayMeta) {
				var label = dayMeta.label || '';
				if (dayMeta.short) {
					label += ' (' + dayMeta.short + ')';
				}
				html += '<option value="' + escapeHtml(String(dayMeta.index)) + '">' + escapeHtml(label) + '</option>';
			});

			select.innerHTML = html;

			if (current !== null && current !== undefined && current !== '') {
				select.value = current;
			}
		}

		function syncFormWeekSelect() {
			if (!form) {
				return;
			}

			var select = form.querySelector('[data-planeacion-field="week_offset"]');
			if (!select) {
				var dayField = form.querySelector('[data-planeacion-field="day"]');
				if (!dayField || !dayField.closest('label')) {
					return;
				}
				var label = document.createElement('label');
				label.innerHTML =
					'<span>Semana</span>' +
					'<select data-planeacion-field="week_offset"></select>';
				dayField.closest('label').insertAdjacentElement('afterend', label);
				select = label.querySelector('[data-planeacion-field="week_offset"]');
			}

			var current = select.value;
			var prevStart = shiftWeek(state.semana_inicio, -1);
			var nextStart = shiftWeek(state.semana_inicio, 1);
			var html =
				'<option value="-1">Semana anterior (' + escapeHtml(formatWeekLabel(prevStart, state.prevWeekDays || buildDaysForWeekStart(prevStart))) + ')</option>' +
				'<option value="0">Esta semana (' + escapeHtml(formatWeekLabel(state.semana_inicio, state.days)) + ')</option>' +
				'<option value="1">Semana siguiente (' + escapeHtml(formatWeekLabel(nextStart, state.nextWeekDays || buildDaysForWeekStart(nextStart))) + ')</option>';

			select.innerHTML = html;
			select.value = String(parseInt(state.editingWeekOffset, 10) || 0);
			if (current !== null && current !== undefined && current !== '') {
				select.value = current;
			}

			select.onchange = function () {
				state.editingWeekOffset = parseInt(select.value, 10) || 0;
				syncFormDaySelect();
			};
		}

		function syncFormDropdowns() {
			if (!form) {
				return;
			}

			form.querySelectorAll('[data-planeacion-options-list]').forEach(function (select) {
				var listKey = select.getAttribute('data-planeacion-options-list');
				var current = select.value;
				var html = '<option value="">?</option>';

				if (listKey === 'papel_materials') {
					var materials = state.fieldOptions.papel_materials || {};
					Object.keys(materials).forEach(function (key) {
						html += '<option value="' + escapeHtml(key) + '">' + escapeHtml(materials[key]) + '</option>';
					});
				} else {
					var options = state.fieldOptions[listKey] || [];
					options.forEach(function (optionValue) {
						html += '<option value="' + escapeHtml(optionValue) + '">' + escapeHtml(optionValue) + '</option>';
					});
				}

				select.innerHTML = html;

				if (current) {
					var hasCurrent = Array.prototype.some.call(select.options, function (option) {
						return option.value === current;
					});
					if (!hasCurrent) {
						var legacyOption = document.createElement('option');
						legacyOption.value = current;
						legacyOption.textContent = current;
						select.appendChild(legacyOption);
					}
					select.value = current;
				}
			});
		}

		function fillOptionsForm() {
			if (!optionsForm) {
				return;
			}

			optionsForm.querySelectorAll('[data-planeacion-options-field]').forEach(function (field) {
				var key = field.getAttribute('data-planeacion-options-field');
				if (key === 'papel_materials') {
					field.value = materialsToTextarea(state.fieldOptions.papel_materials);
					return;
				}
				field.value = checklistToTextarea(state.fieldOptions[key]);
			});
		}

		function openOptionsModal() {
			if (!optionsModal || !optionsForm) {
				return;
			}

			fillOptionsForm();

			if (typeof optionsModal.showModal === 'function') {
				optionsModal.showModal();
			} else {
				optionsModal.setAttribute('open', 'open');
			}
		}

		function closeOptionsModal() {
			if (!optionsModal) {
				return;
			}

			if (typeof optionsModal.close === 'function') {
				optionsModal.close();
			} else {
				optionsModal.removeAttribute('open');
			}
		}

		function saveFieldOptions() {
			if (!optionsForm) {
				return;
			}

			var payload = {};
			optionsForm.querySelectorAll('[data-planeacion-options-field]').forEach(function (field) {
				payload[field.getAttribute('data-planeacion-options-field')] = String(field.value || '');
			});

			setSaveStatus('Guardando listas?', 'is-saving');

			return ajax('logistica_save_planeacion_field_options', {
				options_json: JSON.stringify(payload)
			}).then(function (response) {
				if (!response || !response.success) {
					setSaveStatus((response && response.data && response.data.message) || 'No se pudieron guardar las listas', 'is-error');
					return;
				}

				if (response.data.field_options) {
					state.fieldOptions = loadFieldOptionsConfig({ field_options: response.data.field_options });
				}

				syncFormDropdowns();
				closeOptionsModal();
				setSaveStatus(response.data.message || 'Listas desplegables guardadas');
			}).catch(function () {
				setSaveStatus('Error de conexi?n', 'is-error');
			});
		}

		function isCustomMachine(slug) {
			return state.customMachineSlugs.indexOf(slug) !== -1;
		}

		function syncCustomMachineSlugs(slugs) {
			if (Array.isArray(slugs)) {
				state.customMachineSlugs = slugs.slice();
			}
		}

		function exportPlaneacionExcel() {
			var download = function () {
				window.location.href = buildPlaneacionExportUrl(state.semana_inicio, state.machine);
			};

			if (state.saveTimer) {
				window.clearTimeout(state.saveTimer);
				state.saveTimer = null;
				saveSchedule().then(download).catch(download);
				return;
			}

			download();
		}

		function applyImportedSchedule(schedule, message) {
			if (!schedule) {
				return;
			}

			state.day_hours = normalizeScheduleDayHours(
				schedule.day_hours,
				parseInt(schedule.jornada_inicio, 10) || 420,
				parseInt(schedule.jornada_fin, 10) || 1020
			);
			state.include_lunch = !!schedule.include_lunch;
			var lunch = syncLunchTimesToModule(schedule.lunch_start, schedule.lunch_end);
			state.lunch_start = lunch.lunch_start;
			state.lunch_end = lunch.lunch_end;
			state.tasks = Array.isArray(schedule.tasks) ? schedule.tasks.slice() : [];
			syncLunchControls();
			syncDayHoursToInputs();
			renderBoard();
			setSaveStatus(message || 'Importaci?n completada');
		}

		function importPlaneacionExcel(file) {
			if (!file) {
				return Promise.resolve();
			}

			setSaveStatus('Importando?', 'is-saving');

			var body = new FormData();
			body.append('action', 'logistica_import_planeacion_impresion');
			body.append('nonce', config.planeacionNonce || '');
			body.append('semana_inicio', state.semana_inicio);
			body.append('maquina', state.machine);
			body.append('excel_file', file);

			return fetch(config.ajaxUrl || '/wp-admin/admin-ajax.php', {
				method: 'POST',
				credentials: 'same-origin',
				body: body
			}).then(function (response) {
				return response.text().then(function (text) {
					return parseAjaxResponse(text);
				});
			}).then(function (response) {
				if (!response || !response.success) {
					setSaveStatus((response && response.data && response.data.message) || 'Error al importar', 'is-error');
					return;
				}

				applyImportedSchedule(response.data && response.data.schedule, response.data && response.data.message);
			}).catch(function () {
				setSaveStatus('Error de conexi?n', 'is-error');
			});
		}

		function setSaveStatus(text, type) {
			if (!saveStatus) {
				return;
			}
			saveStatus.textContent = text || '';
			saveStatus.classList.remove('is-saving', 'is-error');
			if (type) {
				saveStatus.classList.add(type);
			}
		}

		function setLoading(isLoading, message) {
			state.loading = isLoading;
			board.classList.toggle('is-loading', isLoading);
		}

		function renderMachinesNav() {
			if (!machinesNav) {
				return;
			}

			var html = '';
			Object.keys(state.machines).forEach(function (slug) {
				var label = state.machines[slug];
				var active = slug === state.machine ? ' is-active' : '';
				html += '<div class="logistica-planeacion__machine-item' + active + '">';
				html += '<button type="button" class="logistica-planeacion__machine-btn" data-planeacion-machine="' + escapeHtml(slug) + '">' + escapeHtml(label) + '</button>';
				html += '<button type="button" class="logistica-planeacion__machine-edit" data-action="planeacion-edit-machine" data-planeacion-machine="' + escapeHtml(slug) + '" title="Editar ' + escapeHtml(label) + '" aria-label="Editar ' + escapeHtml(label) + '">';
				html += '<span class="dashicons dashicons-edit" aria-hidden="true"></span>';
				html += '</button>';
				html += '</div>';
			});
			html += '<button type="button" class="logistica-planeacion__machine-btn logistica-planeacion__machine-btn--add" data-action="planeacion-add-machine" title="Nueva m?quina" aria-label="Nueva m?quina">+</button>';
			machinesNav.innerHTML = html;
		}

		function setMachineModalMode(slug) {
			var isEdit = !!slug;

			if (machineModalTitle) {
				machineModalTitle.textContent = isEdit ? 'Editar m?quina' : 'Nueva m?quina';
			}
			if (machineModalHint) {
				machineModalHint.textContent = isEdit
					? 'Cambia el nombre visible de la m?quina. Al eliminarla se borrar? toda su programaci?n guardada.'
					: 'Cada m?quina tiene programaci?n semanal independiente (tareas, horarios y jornadas propias).';
			}
			if (machineSlugInput) {
				machineSlugInput.value = isEdit ? slug : '';
			}
			if (machineSubmitBtn) {
				machineSubmitBtn.textContent = isEdit ? 'Guardar cambios' : 'Crear m?quina';
			}
			if (machineDeleteBtn) {
				machineDeleteBtn.hidden = !isEdit;
				machineDeleteBtn.disabled = false;
				machineDeleteBtn.title = 'Eliminar esta m?quina y toda su programaci?n';
			}
		}

		function openMachineModal(slug) {
			if (!machineModal || !machineForm) {
				return;
			}

			var editSlug = slug && state.machines[slug] ? slug : '';
			setMachineModalMode(editSlug);

			var nameInput = machineForm.querySelector('[data-planeacion-machine-name]');
			if (nameInput) {
				nameInput.value = editSlug ? state.machines[editSlug] : '';
			}

			if (typeof machineModal.showModal === 'function') {
				machineModal.showModal();
			} else {
				machineModal.setAttribute('open', 'open');
			}

			if (nameInput) {
				nameInput.focus();
				nameInput.select();
			}
		}

		function closeMachineModal() {
			if (!machineModal) {
				return;
			}

			if (typeof machineModal.close === 'function') {
				machineModal.close();
			} else {
				machineModal.removeAttribute('open');
			}
		}

		function addMachine(name) {
			setSaveStatus('Creando m?quina?', 'is-saving');

			return ajax('logistica_add_planeacion_machine', {
				nombre: name
			}).then(function (response) {
				if (!response || !response.success) {
					setSaveStatus((response && response.data && response.data.message) || 'No se pudo crear la m?quina', 'is-error');
					return;
				}

				if (response.data.machines) {
					state.machines = response.data.machines;
				}
				syncCustomMachineSlugs(response.data.custom_machine_slugs);

				renderMachinesNav();
				closeMachineModal();

				if (response.data.slug) {
					setMachine(response.data.slug);
				}

				setSaveStatus(response.data.message || 'M?quina creada');
			}).catch(function () {
				setSaveStatus('Error de conexi?n', 'is-error');
			});
		}

		function updateMachine(slug, name) {
			setSaveStatus('Guardando m?quina?', 'is-saving');

			return ajax('logistica_update_planeacion_machine', {
				maquina: slug,
				nombre: name
			}).then(function (response) {
				if (!response || !response.success) {
					setSaveStatus((response && response.data && response.data.message) || 'No se pudo actualizar la m?quina', 'is-error');
					return;
				}

				if (response.data.machines) {
					state.machines = response.data.machines;
				}
				syncCustomMachineSlugs(response.data.custom_machine_slugs);

				renderMachinesNav();
				closeMachineModal();
				setSaveStatus(response.data.message || 'M?quina actualizada');
			}).catch(function () {
				setSaveStatus('Error de conexi?n', 'is-error');
			});
		}

		function deleteMachine(slug) {
			var label = state.machines[slug] || slug;
			if (!window.confirm('?Eliminar la m?quina "' + label + '"? Se borrar? toda su programaci?n guardada.')) {
				return;
			}

			setSaveStatus('Eliminando m?quina?', 'is-saving');

			return ajax('logistica_delete_planeacion_machine', {
				maquina: slug
			}).then(function (response) {
				if (!response || !response.success) {
					setSaveStatus((response && response.data && response.data.message) || 'No se pudo eliminar la m?quina', 'is-error');
					return;
				}

				if (response.data.machines) {
					state.machines = response.data.machines;
				}
				syncCustomMachineSlugs(response.data.custom_machine_slugs);

				closeMachineModal();

				if (state.machine === slug) {
					var nextSlug = Object.keys(state.machines)[0] || 'sm74';
					state.machine = state.machines[nextSlug] ? nextSlug : resolveStoredMachine(state.machines, 'sm74');
					try {
						window.localStorage.setItem('logistica_planeacion_machine', state.machine);
					} catch (error) {
						// ignore
					}
					loadSchedule();
				} else {
					renderMachinesNav();
				}

				setSaveStatus(response.data.message || 'M?quina eliminada');
			}).catch(function () {
				setSaveStatus('Error de conexi?n', 'is-error');
			});
		}

		function ajax(action, data) {
			var body = new FormData();
			body.append('action', action);
			body.append('nonce', config.planeacionNonce || '');

			Object.keys(data || {}).forEach(function (key) {
				body.append(key, data[key]);
			});

			return fetch(config.ajaxUrl || '/wp-admin/admin-ajax.php', {
				method: 'POST',
				credentials: 'same-origin',
				body: body
			}).then(function (response) {
				return response.text().then(function (text) {
					return parseAjaxResponse(text);
				});
			});
		}

		function applyDayTypeSelection(day, value) {
			var slot = getDaySlot(state.day_hours, day);
			slot.day_type_manual = true;
			slot.day_type = (value != null && String(value).trim() !== '') ? String(value) : '';
			state.day_hours[String(day)] = slot;

			var dayNum = parseInt(day, 10);
			if (isNonWorkingDayType(slot.day_type)) {
				shiftTasksOffClosedDay(state.tasks, dayNum, state.day_hours, state.days, state.calendarConfig);
			} else {
				releaseTasksFromReopenedDays(state.tasks, state.day_hours, state.days, state.calendarConfig);
			}
		}

		function syncDayHoursFromInputs() {
			board.querySelectorAll('[data-planeacion-day-start]').forEach(function (input) {
				var day = input.getAttribute('data-planeacion-day-start');
				var endInput = board.querySelector('[data-planeacion-day-end="' + day + '"]');
				if (!day) {
					return;
				}
				var slot = getDaySlot(state.day_hours, day);
				var start = timeToMinutes(input.value);
				var end = endInput ? timeToMinutes(endInput.value) : null;
				if (!Number.isFinite(start)) {
					start = slot.start;
				}
				if (!Number.isFinite(end) || end <= start) {
					end = slot.end;
				}
				state.day_hours[String(day)] = {
					start: start,
					end: end,
					day_type: slot.day_type,
					day_type_manual: slot.day_type_manual
				};
			});

			board.querySelectorAll('[data-planeacion-day-type]').forEach(function (select) {
				var day = select.getAttribute('data-planeacion-day-type');
				if (!day) {
					return;
				}
				var slot = getDaySlot(state.day_hours, day);
				if (!slot.day_type_manual) {
					return;
				}
				slot.day_type = select.value || '';
				state.day_hours[String(day)] = slot;
			});
		}

		function syncDayHoursToInputs() {
			Object.keys(state.day_hours || {}).forEach(function (day) {
				var slot = getDaySlot(state.day_hours, day);
				var startInput = board.querySelector('[data-planeacion-day-start="' + day + '"]');
				var endInput = board.querySelector('[data-planeacion-day-end="' + day + '"]');
				var typeSelect = board.querySelector('[data-planeacion-day-type="' + day + '"]');
				if (startInput) {
					startInput.value = minutesToTimeInput(slot.start);
				}
				if (endInput) {
					endInput.value = minutesToTimeInput(slot.end);
				}
				if (typeSelect) {
					typeSelect.value = resolveDayTypeForSlot(
						getScheduledDayDate(day, state.days),
						slot,
						state.calendarConfig
					);
				}
			});
		}

		function updateDayColumnLabels() {
			board.querySelectorAll('[data-planeacion-day]').forEach(function (column) {
				var dayIndex = parseInt(column.getAttribute('data-planeacion-day'), 10);
				var dayMeta = null;

				(state.days || []).forEach(function (entry) {
					if (parseInt(entry.index, 10) === dayIndex) {
						dayMeta = entry;
					}
				});

				if (!dayMeta) {
					return;
				}

				var titleWrap = column.querySelector('.logistica-planeacion__day-title');
				if (!titleWrap) {
					return;
				}

				var titleNode = titleWrap.querySelector('strong');
				var dateNode = titleWrap.querySelector('span');

				if (titleNode && dayMeta.label) {
					titleNode.textContent = dayMeta.label;
				}

				if (dateNode) {
					dateNode.textContent = dayMeta.short || dayMeta.date || '';
				}
			});

			updateWeekLabel();
		}

		function updateDayColumnsState() {
			updateDayColumnLabels();
			board.querySelectorAll('[data-planeacion-day]').forEach(function (column) {
				var day = column.getAttribute('data-planeacion-day');
				var date = getScheduledDayDate(day, state.days);
				var slot = getDaySlot(state.day_hours, day);
				var dayType = resolveDayTypeForSlot(date, slot, state.calendarConfig);
				var isOff = isNonWorkingDayType(dayType);
				var config = getNonWorkingDayViewConfig(dayType, state.calendarConfig.day_types);
				var hoursWrap = column.querySelector('.logistica-planeacion__day-hours');
				var addBtn = column.querySelector('[data-action="planeacion-add-task-day"]');
				var typeSelect = column.querySelector('[data-planeacion-day-type]');
				var offBlock = column.querySelector('[data-planeacion-day-off]');
				var empty = column.querySelector('[data-planeacion-empty]');
				var dropzone = column.querySelector('[data-planeacion-dropzone]');
				var timelineScroll = column.querySelector('[data-planeacion-timeline-scroll]');

				column.classList.toggle('is-day-off', isOff);
				column.classList.toggle('is-festivo', dayType === 'festivo');
				column.classList.toggle('is-libre', dayType === 'libre');
				column.classList.toggle('is-vacaciones', dayType === 'vacaciones');
				column.classList.toggle('is-permisos', dayType === 'permisos');

				if (typeSelect) {
					typeSelect.value = dayType;
					syncDayTypeSelectClasses(typeSelect, dayType, isOff);
				}

				if (hoursWrap) {
					hoursWrap.querySelectorAll('input').forEach(function (input) {
						input.disabled = isOff;
					});
				}

				if (addBtn) {
					addBtn.disabled = isOff;
					addBtn.hidden = isOff;
				}

				if (dropzone) {
					dropzone.classList.toggle('is-disabled', isOff);
				}

				if (timelineScroll) {
					timelineScroll.hidden = isOff;
				}

				if (offBlock) {
					offBlock.hidden = !isOff;
					if (isOff) {
						var labelNode = offBlock.querySelector('[data-planeacion-day-off-label]');
						var descNode = offBlock.querySelector('[data-planeacion-day-off-desc]');
						if (labelNode) {
							labelNode.textContent = config.title;
						}
						if (descNode) {
							descNode.textContent = config.description;
						}
						var iconNode = offBlock.querySelector('.logistica-planeacion__day-off-icon');
						if (iconNode) {
							iconNode.className = 'logistica-planeacion__day-off-icon dashicons ' + config.icon;
						}
					}
				}

				if (empty) {
					empty.hidden = isOff;
				}
			});
		}

		function minutesToTimeInput(minutes) {
			if (minutes === null || minutes === undefined) {
				return '';
			}
			minutes = Math.max(0, Math.round(minutes));
			var h = Math.floor(minutes / 60);
			var m = minutes % 60;
			return String(h).padStart(2, '0') + ':' + String(m).padStart(2, '0');
		}

		function normalizeTimeInputValue(minutes) {
			return minutesToTimeInput(minutes);
		}

		function updateWeekLabel() {
			if (weekLabel) {
				weekLabel.textContent = formatWeekLabel(state.semana_inicio, state.days);
			}
		}

		function getGloballyOrderedTasks() {
			return state.tasks.slice().sort(compareGlobalTaskOrder);
		}

		function getTasksForDay(day) {
			return state.tasks
				.filter(function (task) {
					return parseInt(task.day, 10) === parseInt(day, 10);
				})
				.sort(function (a, b) {
					return (a.sort_order || 0) - (b.sort_order || 0);
				});
		}

		function segmentMatchesColumnDayForDisplay(seg, dayNum, columnDate, displayWeekOffset, carryoverShift, options) {
			options = options || {};
			if (options.matchByDateOnly || displayWeekOffset !== 0) {
				if (columnDate && seg.date) {
					return seg.date === columnDate;
				}
				return parseInt(seg.day, 10) === parseInt(dayNum, 10);
			}
			return segmentMatchesColumnDay(seg, dayNum, columnDate, displayWeekOffset, carryoverShift);
		}

		function collectDisplayItemsForTask(items, task, dayNum, displayWeekOffset, carryoverShift, options) {
			options = options || {};
				var segments = task.schedule_segments;
			var isCarryover = !!options.isCarryover;
			var days = options.days || state.days;
			var columnDate = getScheduledDayDate(dayNum, days);
			var matchOptions = {
				matchByDateOnly: !!options.matchByDateOnly || displayWeekOffset !== 0
			};

				if (!segments || !segments.length) {
				if (parseInt(task.day, 10) === dayNum && (displayWeekOffset === 0 || matchOptions.matchByDateOnly)) {
						items.push({
							task: task,
							segment: {
								day: dayNum,
							date: columnDate || '',
							week_offset: displayWeekOffset || 0,
								start_minutes: task.start_minutes,
								end_minutes: task.end_minutes,
								minutes: task.duration_minutes,
								is_continuation: false
							},
							isPrimary: true,
						isCarryover: isCarryover,
							sortOrder: task.sort_order || 0
						});
					}
					return;
				}

				segments.forEach(function (seg, index) {
				if (!segmentMatchesColumnDayForDisplay(seg, dayNum, columnDate, displayWeekOffset, carryoverShift, matchOptions)) {
						return;
					}

					items.push({
						task: task,
						segment: seg,
						segmentIndex: index,
						segmentCount: segments.length,
					isPrimary: index === 0 && !isCarryover && !seg.is_continuation,
					isCarryover: isCarryover,
						sortOrder: seg.is_continuation ? (task.sort_order || 0) + 0.001 + (index * 0.0001) : (task.sort_order || 0)
					});
				});
		}

		function dedupeDisplayItems(items) {
			var seen = {};
			var unique = [];

			items.forEach(function (item) {
				var seg = item.segment;
				var key = [
					item.task.id,
					seg.date || '',
					seg.week_offset || 0,
					seg.start_minutes,
					seg.end_minutes
				].join(':');

				if (seen[key]) {
					return;
				}

				seen[key] = true;
				unique.push(item);
			});

			return unique.filter(function (item, index) {
				var seg = item.segment;
				for (var i = 0; i < unique.length; i++) {
					if (i === index) {
						continue;
					}
					var other = unique[i];
					var otherSeg = other.segment;
					if (seg.start_minutes >= otherSeg.end_minutes || otherSeg.start_minutes >= seg.end_minutes) {
						continue;
					}
					if (other.task.id === item.task.id) {
						return false;
					}
					if ((item.sortOrder || 0) > (other.sortOrder || 0)) {
						return false;
					}
				}
				return true;
			});
		}

		function isDuplicateCarryoverTask(task) {
			var key = taskSchedulingKey(task);
			var match = null;

			state.tasks.some(function (current) {
				if (taskSchedulingKey(current) === key) {
					match = current;
					return true;
				}
				return false;
			});

			if (!match) {
				return false;
			}

			return (match.schedule_segments || []).length > 0;
		}

		function getDisplayItemsForDay(day, options) {
			options = options || {};
			var dayNum = parseInt(day, 10);
			var days = options.days || state.days;
			var dayHours = options.dayHours || state.day_hours;
			var displayWeekOffset = options.displayWeekOffset || 0;
			var carryoverShift = options.carryoverShift || 0;
			var includeCarryover = options.includeCarryover !== false;
			var taskSource = options.taskSource || getTaskSourcesForWeekOffset(displayWeekOffset);
			var collectOptions = { days: days };

			if (isDayNonWorking(dayNum, dayHours, days, state.calendarConfig)) {
				return [];
			}

			var items = [];

			taskSource.forEach(function (task) {
				collectDisplayItemsForTask(items, task, dayNum, displayWeekOffset, carryoverShift, collectOptions);
			});

			if (includeCarryover && displayWeekOffset === 0 && carryoverShift === 0) {
				(state.carryoverTasks || []).forEach(function (entry) {
					if (isDuplicateCarryoverTask(entry.task)) {
						return;
					}
					collectDisplayItemsForTask(items, entry.task, dayNum, 0, 1, {
						isCarryover: true,
						days: days
					});
				});
			}

			return dedupeDisplayItems(items.sort(function (a, b) {
				var startDiff = (a.segment.start_minutes || 0) - (b.segment.start_minutes || 0);
				if (startDiff !== 0) {
					return startDiff;
				}
				return (a.sortOrder || 0) - (b.sortOrder || 0);
			}));
		}

		function getOverflowWeekOffsets() {
			var offsets = {};

			state.tasks.forEach(function (task) {
				(task.schedule_segments || []).forEach(function (seg) {
					var weekOffset = parseInt(seg.week_offset, 10) || 0;
					if (weekOffset > 0) {
						offsets[weekOffset] = true;
					}
				});
			});

			return Object.keys(offsets).map(function (key) {
				return parseInt(key, 10);
			}).sort(function (a, b) {
				return a - b;
			});
		}

		function getBoardGrid() {
			return board.querySelector('[data-planeacion-grid]') || board;
		}

		function renderAdjacentWeekStrip(stripKey, weekOffset, grid) {
			var ctx = getWeekContextByOffset(weekOffset);
			if (weekOffset < 0 && (!state.prevWeekDays || !state.prevWeekDays.length)) {
				return;
			}
			if (weekOffset > 0 && (!state.nextWeekDays || !state.nextWeekDays.length)) {
				return;
			}

			var stripType = stripKey === 'prev' ? 'prev' : 'next';
			var days = filterDaysForAdjacentStrip(ctx.days || [], stripType);
			if (!days.length) {
				return;
			}

			var expanded = !!state.adjacentStripExpanded[stripKey];
			var strip = document.createElement('div');
			strip.className = 'logistica-planeacion__adjacent-strip logistica-planeacion__adjacent-strip--' + stripKey +
				(expanded ? ' is-expanded' : ' is-collapsed');
			strip.setAttribute('data-adjacent-strip', stripKey);
			strip.setAttribute('data-expanded', expanded ? 'true' : 'false');

			var weekStart = shiftWeek(state.semana_inicio, weekOffset);
			var weekLabel = formatWeekLabel(weekStart, ctx.days);
			var dayRangeLabel = getAdjacentStripDayRangeLabel(stripType, ctx.days);
			var stripTitle = stripKey === 'prev' ? 'Semana anterior' : 'Semana siguiente';

			var toggle = document.createElement('button');
			toggle.type = 'button';
			toggle.className = 'logistica-planeacion__adjacent-strip-toggle';
			toggle.setAttribute('data-action', 'planeacion-toggle-adjacent');
			toggle.setAttribute('data-adjacent', stripKey);
			toggle.setAttribute('aria-expanded', expanded ? 'true' : 'false');
			toggle.innerHTML =
				'<span class="logistica-planeacion__adjacent-strip-toggle-icon dashicons ' +
					(expanded ? 'dashicons-arrow-down-alt2' : 'dashicons-arrow-right-alt2') +
				'" aria-hidden="true"></span>' +
				'<span class="logistica-planeacion__adjacent-strip-toggle-text">' +
					escapeHtml(stripTitle) + ' <small>(' + escapeHtml(dayRangeLabel) + ')</small>' +
				'</span>' +
				'<span class="logistica-planeacion__adjacent-strip-toggle-meta">' + escapeHtml(weekLabel) + '</span>';

			var daysWrap = document.createElement('div');
			daysWrap.className = 'logistica-planeacion__adjacent-strip-days';
			if (!expanded) {
				daysWrap.hidden = true;
			}

			days.forEach(function (dayMeta) {
				var column = buildAdjacentWeekDayColumn(dayMeta, weekOffset, ctx);
				var zone = column.querySelector('[data-planeacion-dropzone]');
				if (zone) {
					renderTimelineZone(zone, {
						displayWeekOffset: weekOffset,
						days: ctx.days,
						dayHours: ctx.dayHours,
						includeCarryover: false,
						taskSource: getTaskSourcesForWeekOffset(weekOffset)
					});
				}
				daysWrap.appendChild(column);
			});

			strip.appendChild(toggle);
			strip.appendChild(daysWrap);

			var anchorDay = stripKey === 'prev' ? '0' : '6';
			var anchorCol = grid.querySelector(
				'.logistica-planeacion__day[data-planeacion-day="' + anchorDay + '"]:not(.logistica-planeacion__day--adjacent)'
			);

			if (stripKey === 'prev' && anchorCol) {
				grid.insertBefore(strip, anchorCol);
				return;
			}

			if (stripKey === 'next' && anchorCol) {
				if (anchorCol.nextElementSibling) {
					grid.insertBefore(strip, anchorCol.nextElementSibling);
				} else {
					grid.appendChild(strip);
				}
				return;
			}

			if (stripKey === 'prev') {
				grid.insertBefore(strip, grid.firstChild);
			} else {
				var overflowRoot = grid.querySelector('[data-planeacion-overflow-weeks]');
				if (overflowRoot) {
					grid.insertBefore(strip, overflowRoot);
				} else {
					grid.appendChild(strip);
				}
			}
		}

		function renderAdjacentWeekStrips() {
			var grid = getBoardGrid();
			if (!grid) {
				return;
			}

			grid.querySelectorAll('[data-adjacent-strip]').forEach(function (node) {
				node.remove();
			});

			renderAdjacentWeekStrip('prev', -1, grid);
			renderAdjacentWeekStrip('next', 1, grid);

			var overflowRoot = grid.querySelector('[data-planeacion-overflow-weeks]');
			if (overflowRoot) {
				overflowRoot.hidden = true;
				overflowRoot.innerHTML = '';
			}

			var legacyPrior = board.querySelector('[data-planeacion-prior-week]');
			if (legacyPrior) {
				legacyPrior.remove();
			}
		}

		function toggleAdjacentWeekStrip(stripKey) {
			if (!state.adjacentStripExpanded || !Object.prototype.hasOwnProperty.call(state.adjacentStripExpanded, stripKey)) {
				return;
			}
			state.adjacentStripExpanded[stripKey] = !state.adjacentStripExpanded[stripKey];
			renderAdjacentWeekStrips();
		}

		function buildOverflowDayTypeLabel(dayMeta, dayHours) {
			var slot = getDaySlot(dayHours, dayMeta.index);
			var dayType = resolveDayTypeForSlot(dayMeta.date, slot, state.calendarConfig);
			var config = getNonWorkingDayViewConfig(dayType, state.calendarConfig.day_types);
			return isNonWorkingDayType(dayType) ? config.title : 'Laborable';
		}

		function buildAdjacentWeekDayColumn(dayMeta, weekOffset, ctx, options) {
			options = options || {};
			var dayIndex = parseInt(dayMeta.index, 10);
			var date = dayMeta.date || '';
			var slot = getDaySlot(ctx.dayHours, dayIndex);
			var dayTypeLabel = buildOverflowDayTypeLabel(dayMeta, ctx.dayHours);
			var isOff = isDayNonWorking(dayIndex, ctx.dayHours, ctx.days, state.calendarConfig);
			var priorClass = weekOffset < 0 ? ' logistica-planeacion__day--prior' : '';
			var column = document.createElement('div');
			column.className = 'logistica-planeacion__day logistica-planeacion__day--overflow logistica-planeacion__day--adjacent' + priorClass + (isOff ? ' is-day-off' : '');
			column.setAttribute('data-planeacion-day', String(dayIndex));
			column.setAttribute('data-planeacion-week-offset', String(weekOffset));

			column.innerHTML =
				'<header class="logistica-planeacion__day-header">' +
					'<div class="logistica-planeacion__day-title">' +
						'<strong>' + escapeHtml(dayMeta.label || '') + '</strong>' +
						'<span>' + escapeHtml(dayMeta.short || date) + '</span>' +
					'</div>' +
					'<div class="logistica-planeacion__day-type logistica-planeacion__day-type--readonly">' +
						escapeHtml(dayTypeLabel) +
					'</div>' +
					'<div class="logistica-planeacion__day-hours logistica-planeacion__day-hours--readonly">' +
						escapeHtml(formatRange(slot.start, slot.end)) +
					'</div>' +
				'</header>' +
				'<div class="logistica-planeacion__day-body logistica-planeacion__day-body--adjacent" data-planeacion-dropzone="' + dayIndex + '" data-planeacion-week-offset="' + weekOffset + '">' +
					(isOff
						? '<div class="logistica-planeacion__day-off" data-planeacion-day-off>' +
							'<span class="logistica-planeacion__day-off-icon dashicons dashicons-star-filled" aria-hidden="true"></span>' +
							'<div class="logistica-planeacion__day-off-body">' +
								'<strong>' + escapeHtml(dayTypeLabel) + '</strong>' +
								'<p>D?a no laborable en esta semana.</p>' +
							'</div>' +
						'</div>'
						: '') +
					'<div class="logistica-planeacion__timeline-scroll" data-planeacion-timeline-scroll' + (isOff ? ' hidden' : '') + '>' +
						'<div class="logistica-planeacion__timeline" data-planeacion-timeline>' +
							'<div class="logistica-planeacion__timeline-ruler" data-planeacion-timeline-ruler aria-hidden="true"></div>' +
							'<div class="logistica-planeacion__timeline-track" data-planeacion-timeline-track></div>' +
						'</div>' +
					'</div>' +
					'<p class="logistica-planeacion__empty" data-planeacion-empty hidden>Sin actividades en esta semana.</p>' +
				'</div>';

			return column;
		}

		function buildOverflowDayColumn(dayMeta, weekOffset, ctx) {
			return buildAdjacentWeekDayColumn(dayMeta, weekOffset, ctx);
		}

		function renderTimelineZone(zone, contextOptions) {
			contextOptions = contextOptions || {};
			var day = zone.getAttribute('data-planeacion-dropzone');
			var displayWeekOffset = parseInt(
				contextOptions.displayWeekOffset != null
					? contextOptions.displayWeekOffset
					: (zone.getAttribute('data-planeacion-week-offset') || '0'),
				10
			) || 0;
			var days = contextOptions.days || state.days;
			var dayHours = contextOptions.dayHours || state.day_hours;
			var includeCarryover = contextOptions.includeCarryover !== false;
			var items = getDisplayItemsForDay(day, {
				displayWeekOffset: displayWeekOffset,
				days: days,
				dayHours: dayHours,
				includeCarryover: includeCarryover
			});
			var empty = zone.querySelector('[data-planeacion-empty]');
			var track = zone.querySelector('[data-planeacion-timeline-track]');
			var metrics = applyTimelineLayout(
				zone,
				day,
				items,
				dayHours,
				days,
				state.calendarConfig
			);

			if (track) {
				items.forEach(function (item, index) {
					item.weekOffset = displayWeekOffset;
					var card = renderTaskCard(item, index, items.length, day);
					if (metrics) {
						positionTaskOnTimeline(card, item.segment, metrics);
					}
					track.appendChild(card);
				});
				if (metrics) {
					renderSegmentConnectors(track, items, metrics);
				}
			}

			if (empty) {
				var isOff = isDayNonWorking(
					parseInt(day, 10),
					dayHours,
					days,
					state.calendarConfig
				);
				empty.hidden = isOff || items.length > 0 || !!metrics;
			}
		}

		function buildTaskMetaItem(label, value, modifierClass) {
			if (!value) {
				return '';
			}

			return '<div class="logistica-planeacion__task-detail' + (modifierClass ? ' ' + modifierClass : '') + '">' +
				'<span class="logistica-planeacion__task-detail-label">' + escapeHtml(label) + '</span>' +
				'<span class="logistica-planeacion__task-detail-value">' + escapeHtml(value) + '</span>' +
			'</div>';
		}

		function formatTirajeDisplay(task) {
			var count = parseTamanos(task.tamanos);
			var raw = String(task.tamanos || '').trim();

			if (count) {
				return count.toLocaleString('es-CO') + ' tama?os';
			}

			return raw;
		}

		function buildTaskMetaHtml(task, laborType, options) {
			options = options || {};
			var isContinuation = !!options.isContinuation;
			var segmentMinutes = options.segmentMinutes || 0;
			var items = [];

			normalizePapelFields(task);

			if (laborFieldVisible(laborType, 'tamanos') && task.tamanos) {
				items.push(buildTaskMetaItem('Tiraje', formatTirajeDisplay(task)));
			}

			if (laborFieldVisible(laborType, 'tintas') && (task.tintas || task.colores)) {
				if (task.tintas) {
					items.push(buildTaskMetaItem('Tintas', String(task.tintas).trim()));
				}
				if (task.colores) {
					items.push(buildTaskMetaItem('Colores', String(task.colores).trim()));
				}
			}

			if (laborFieldVisible(laborType, 'papel')) {
				var papelLabel = formatPapelSummary(task);
				if (papelLabel) {
					items.push(buildTaskMetaItem('Papel', papelLabel));
				}
			}

			var durationMinutes = isContinuation ? segmentMinutes : task.duration_minutes;

			items.push(buildTaskMetaItem(
				'Duraci?n',
				formatDurationLabel(durationMinutes),
				'logistica-planeacion__task-detail--duration'
			));

			return items.join('');
		}

		function hasPreviewValue(value) {
			if (value === null || value === undefined) {
				return false;
			}

			var text = String(value).trim();
			return text !== '' && text !== '?' && text !== '-';
		}

		function getDayLabel(dayIndex) {
			var match = (state.days || []).find(function (day) {
				return parseInt(day.index, 10) === parseInt(dayIndex, 10);
			});
			return match ? match.label : '';
		}

		function buildTaskPreviewRow(label, value) {
			return '<div class="logistica-planeacion__preview-row">' +
				'<span class="logistica-planeacion__preview-label">' + escapeHtml(label) + '</span>' +
				'<span class="logistica-planeacion__preview-value">' + escapeHtml(value) + '</span>' +
			'</div>';
		}

		function collectTaskSummaryRows(task, previewContext, options) {
			previewContext = previewContext || {};
			options = options || {};
			var basicOnly = !!options.basicOnly;
			normalizePapelFields(task);

			var laborType = sanitizeLaborType(task.labor_type || 'otro');
			var laborLabel = getLaborLabel(laborType);
			var isContinuation = !!previewContext.isContinuation;
			var rows = [];

			function pushRow(label, value) {
				if (!hasPreviewValue(value)) {
					return;
				}
				rows.push({ label: label, value: String(value).trim() });
			}

			var start = previewContext.start_minutes;
			var end = previewContext.end_minutes;
			if (start !== null && start !== undefined && end !== null && end !== undefined) {
				var schedule = formatRange(start, end);
				if (!isContinuation && task.continues_next_day) {
					schedule += ' ?';
				}
				pushRow('Horario', schedule);
			}

			if (isContinuation) {
				pushRow('Segmento', 'Continuaci?n');
			}

			pushRow('OP', task.op);
			if (!basicOnly) {
				pushRow('D?a', getDayLabel(task.day));
			}
			pushRow('Estado', state.statuses[task.status] || task.status);
			pushRow('Tipo de labor', laborLabel);

			var laborText = String(task.labor || '').trim();
			if (laborText && laborText.toLowerCase() !== laborLabel.toLowerCase()) {
				pushRow('Descripci?n', laborText);
			}

			if (laborFieldVisible(laborType, 'tamanos') && hasPreviewValue(task.tamanos)) {
				pushRow('Tiraje', formatTirajeDisplay(task));
			}

			if (!basicOnly && laborFieldVisible(laborType, 'tamanos') && task.complexity && task.complexity !== 'normal') {
				pushRow('Complejidad', state.complexities[task.complexity] || task.complexity);
			}

			if (laborFieldVisible(laborType, 'tintas')) {
				var tintasSummary = formatTintasSummaries(ensureTintasConfig(task));
				pushRow('Tintas', tintasSummary.tintas);
				pushRow('Colores', tintasSummary.colores);
				if (!tintasSummary.tintas && hasPreviewValue(task.tintas)) {
					pushRow('Tintas', task.tintas);
				}
				if (!tintasSummary.colores && hasPreviewValue(task.colores)) {
					pushRow('Colores', task.colores);
				}
			}

			if (laborFieldVisible(laborType, 'papel')) {
				pushRow('Papel', formatPapelSummary(task));
			}

			if (!basicOnly) {
				var checklistLabels = {
					montaje: 'Montaje',
					prep_tinta: 'Preparaci?n de tintas',
					planchas: 'Planchas',
					meta: 'Cumplimiento de metas'
				};

				CHECKLIST_FIELDS.forEach(function (fieldKey) {
					if (!laborFieldVisible(laborType, fieldKey)) {
						return;
					}
					pushRow(checklistLabels[fieldKey] || fieldKey, task[fieldKey]);
				});
			}

			var durationMinutes = isContinuation ? previewContext.segmentMinutes : task.duration_minutes;
			pushRow('Duraci?n', formatDurationLabel(durationMinutes));

			if (!basicOnly) {
				if (task.actual_start !== null && task.actual_start !== undefined) {
					pushRow('Inicio real', minutesToTime(task.actual_start));
				}
				if (task.actual_end !== null && task.actual_end !== undefined) {
					pushRow('Fin real', minutesToTime(task.actual_end));
				}
				pushRow('Notas', task.notes);
			}

			return rows;
		}

		function buildTaskTooltipText(task, previewContext) {
			return collectTaskSummaryRows(task, previewContext, { basicOnly: true }).map(function (row) {
				return row.label + ': ' + row.value;
			}).join('\n');
		}

		function buildTaskPreviewHtml(task, previewContext) {
			var rows = collectTaskSummaryRows(task, previewContext, { basicOnly: false });

			if (!rows.length) {
				return '<p class="logistica-planeacion__preview-empty">Sin datos para mostrar.</p>';
			}

			return rows.map(function (row) {
				return buildTaskPreviewRow(row.label, row.value);
			}).join('');
		}

		function syncTaskCardTooltips() {
			board.querySelectorAll('.logistica-planeacion__task--timeline').forEach(function (card) {
				card.classList.add('logistica-planeacion__task--tip-visible');
				card.removeAttribute('title');
			});
		}

		var taskFloatTipEl = null;
		var taskFloatTipTrigger = null;

		function ensureTaskFloatTip() {
			if (taskFloatTipEl) {
				return taskFloatTipEl;
			}

			taskFloatTipEl = document.createElement('div');
			taskFloatTipEl.className = 'logistica-planeacion__task-tip-float';
			taskFloatTipEl.setAttribute('role', 'tooltip');
			taskFloatTipEl.hidden = true;
			document.body.appendChild(taskFloatTipEl);
			return taskFloatTipEl;
		}

		function hideTaskFloatTip() {
			if (taskFloatTipEl) {
				taskFloatTipEl.hidden = true;
				taskFloatTipEl.textContent = '';
				taskFloatTipEl.classList.remove('is-below');
				taskFloatTipEl.style.left = '';
				taskFloatTipEl.style.top = '';
			}
			taskFloatTipTrigger = null;
		}

		function positionTaskFloatTip(trigger, tip) {
			tip.style.left = '-9999px';
			tip.style.top = '0';

			var rect = trigger.getBoundingClientRect();
			var tipRect = tip.getBoundingClientRect();
			var gap = 10;
			var pad = 12;
			var left = rect.left + (rect.width / 2) - (tipRect.width / 2);
			left = Math.max(pad, Math.min(left, window.innerWidth - tipRect.width - pad));

			var top = rect.top - tipRect.height - gap;
			tip.classList.remove('is-below');
			if (top < pad) {
				top = rect.bottom + gap;
				tip.classList.add('is-below');
			}

			tip.style.left = Math.round(left) + 'px';
			tip.style.top = Math.round(top) + 'px';
		}

		function showTaskFloatTip(card) {
			if (!card || !card.classList.contains('logistica-planeacion__task--timeline')) {
				return;
			}

			var tipText = card.dataset.planeacionTooltip || '';
			if (!tipText) {
				hideTaskFloatTip();
				return;
			}

			var tip = ensureTaskFloatTip();
			taskFloatTipTrigger = card;
			tip.textContent = tipText;
			tip.hidden = false;
			positionTaskFloatTip(card, tip);
		}

		function bindTaskFloatTipEvents() {
			board.addEventListener('mouseover', function (event) {
				var card = event.target.closest('.logistica-planeacion__task--timeline');
				if (card) {
					showTaskFloatTip(card);
				}
			});

			board.addEventListener('mouseout', function (event) {
				if (!taskFloatTipTrigger) {
					return;
				}

				var card = event.target.closest('.logistica-planeacion__task--timeline');
				if (card !== taskFloatTipTrigger) {
					return;
				}

				var related = event.relatedTarget;
				if (!related || !card.contains(related)) {
					hideTaskFloatTip();
				}
			});

			board.addEventListener('scroll', hideTaskFloatTip, true);
			window.addEventListener('scroll', hideTaskFloatTip, true);
			window.addEventListener('resize', hideTaskFloatTip);
		}

		function scheduleTaskCardTooltipSync() {
			window.requestAnimationFrame(function () {
				syncTaskCardTooltips();
			});
		}

		function ensureTaskPreviewPanel() {
			if (!form || form.querySelector('[data-planeacion-task-preview]')) {
				return;
			}

			var body = form.querySelector('.logistica-planeacion__modal-body');
			var formGrid = form.querySelector('.logistica-planeacion__form-grid');
			if (!body || !formGrid) {
				return;
			}

			var preview = document.createElement('div');
			preview.className = 'logistica-planeacion__task-preview';
			preview.setAttribute('data-planeacion-task-preview', '');
			preview.hidden = true;
			body.insertBefore(preview, formGrid);
		}

		function setModalPreviewMode(previewMode, task, previewContext) {
			ensureTaskPreviewPanel();

			var previewEl = form ? form.querySelector('[data-planeacion-task-preview]') : null;
			var formGrid = form ? form.querySelector('.logistica-planeacion__form-grid') : null;

			if (form) {
				form.classList.toggle('is-preview-mode', previewMode);
			}

			if (formGrid) {
				formGrid.hidden = previewMode;
			}

			if (previewEl) {
				previewEl.hidden = !previewMode;
				previewEl.innerHTML = previewMode && task ? buildTaskPreviewHtml(task, previewContext) : '';
			}
		}

		function readTaskCardPreviewContext(card, task) {
			var context = {
				isContinuation: card.dataset.segmentContinuation === '1',
				segmentMinutes: parseInt(card.dataset.segmentMinutes, 10) || 0,
				start_minutes: null,
				end_minutes: null
			};

			if (card.dataset.segmentStart !== undefined && card.dataset.segmentStart !== '') {
				context.start_minutes = parseInt(card.dataset.segmentStart, 10);
			}
			if (card.dataset.segmentEnd !== undefined && card.dataset.segmentEnd !== '') {
				context.end_minutes = parseInt(card.dataset.segmentEnd, 10);
			}

			if ((context.start_minutes === null || context.end_minutes === null) && task) {
				context.start_minutes = task.start_minutes;
				context.end_minutes = task.end_minutes;
			}

			return context;
		}

		function buildStatusSelect(currentStatus) {
			var html = '<select class="logistica-planeacion__task-status logistica-planeacion__task-status--' + escapeHtml(currentStatus) + '" data-planeacion-task-status aria-label="Estado">';
			Object.keys(state.statuses).forEach(function (key) {
				var label = state.statuses[key];
				html += '<option value="' + escapeHtml(key) + '"' + (key === currentStatus ? ' selected' : '') + '>' + escapeHtml(label) + '</option>';
			});
			html += '</select>';
			return html;
		}

		function applyStatusSideEffects(task, newStatus) {
			if (newStatus === 'in_progress') {
				task.planned_day = task.planned_day !== undefined && task.planned_day !== null
					? parseInt(task.planned_day, 10)
					: parseInt(task.day, 10);
				task.started_on_day = parseInt(task.day, 10);
				task.actual_start = task.start_minutes;
				task.actual_end = null;
				return;
			}

			if (newStatus === 'done') {
				if (task.actual_start === null || task.actual_start === undefined) {
					task.actual_start = task.start_minutes;
				}
				task.actual_end = task.end_minutes;
				task.reschedule_delay = false;
				return;
			}

			if (newStatus === 'pending') {
				task.reschedule_delay = false;
				task.started_on_day = null;
			}

			task.actual_start = null;
			task.actual_end = null;
		}

		function setTaskStatus(id, newStatus) {
			if (!state.statuses[newStatus]) {
				return;
			}

			syncDayHoursFromInputs();
			runScheduleRecalculate();

			var task = findTask(id);
			if (!task) {
				return;
			}

			task.status = newStatus;
			task.status_manual = true;
			applyStatusSideEffects(task, newStatus);
			renderBoard();
			scheduleSave();
		}

		function getTaskDisplayDescription(task, laborLabel) {
			var laborText = String(task.labor || '').trim();
			if (!laborText) {
				return '';
			}
			if (laborText.toLowerCase() === String(laborLabel || '').trim().toLowerCase()) {
				return '';
			}
			return laborText;
		}

		function renderTaskCard(displayItem, index, totalInDay, columnDay) {
			var task = displayItem.task;
			var segment = displayItem.segment;
			var isContinuation = !!segment.is_continuation;
			var isPrimary = !!displayItem.isPrimary && !displayItem.isCarryover;
			var isCarryover = !!displayItem.isCarryover;

			normalizeTaskLabor(task);
			var card = document.createElement('article');
			var status = task.status || 'pending';
			var laborType = sanitizeLaborType(task.labor_type || 'otro');
			card.className = 'logistica-planeacion__task logistica-planeacion__task--' + status + ' logistica-planeacion__task--labor-' + laborType;
			if (isContinuation) {
				card.classList.add('logistica-planeacion__task--continuation');
			}
			if (isCarryover) {
				card.classList.add('logistica-planeacion__task--carryover');
			}
			if (isPrimary && task.continues_next_day) {
				card.classList.add('logistica-planeacion__task--spills-next-day');
			}
			if (isPrimary && task.continues_next_week) {
				card.classList.add('logistica-planeacion__task--spills-next-week');
			}
			card.draggable = isPrimary;
			card.dataset.taskId = task.id;
			card.dataset.day = String(task.day);
			card.dataset.columnDay = String(columnDay != null ? columnDay : task.day);
			card.dataset.weekOffset = String(displayItem.weekOffset != null ? displayItem.weekOffset : 0);
			if (isContinuation) {
				card.dataset.segmentContinuation = '1';
			}
			card.dataset.segmentStart = String(segment.start_minutes);
			card.dataset.segmentEnd = String(segment.end_minutes);
			card.dataset.segmentMinutes = String(segment.minutes || 0);

			if (window.LogisticaPlaneacionTaskColors) {
				LogisticaPlaneacionTaskColors.applyTaskAccentColor(
					card,
					task.id,
					displayItem.weekOffset != null ? displayItem.weekOffset : 0
				);
			}

			var laborLabel = getLaborLabel(laborType);

			var linkInfo = analyzeSegmentContinuationLink(task, segment, displayItem);

			if (linkInfo.isAfterLunch) {
				card.classList.add('logistica-planeacion__task--after-lunch');
			}
			if (linkInfo.continuesAfterLunch) {
				card.classList.add('logistica-planeacion__task--continues-after-lunch');
			}
			if (linkInfo.continuesSameDayLater && !linkInfo.continuesAfterLunch) {
				card.classList.add('logistica-planeacion__task--continues-later');
			}
			if (linkInfo.continuesNextDay && isPrimary) {
				card.classList.add('logistica-planeacion__task--continues-future-day');
			}

			var descriptionText = getTaskDisplayDescription(task, laborLabel);
			var opText = task.op ? escapeHtml(task.op) : 'SIN OP';
			var descHtml = descriptionText
				? '<span class="logistica-planeacion__task-timeline-desc" title="' + escapeHtml(descriptionText) + '">' + escapeHtml(descriptionText) + '</span>'
				: '';

			var cardPreviewContext = {
				isContinuation: isContinuation,
				segmentMinutes: segment.minutes,
				start_minutes: segment.start_minutes,
				end_minutes: segment.end_minutes
			};
			card.dataset.planeacionTooltip = buildTaskTooltipText(task, cardPreviewContext);

			card.innerHTML =
				'<div class="logistica-planeacion__task-timeline">' +
					'<div class="logistica-planeacion__task-timeline-tools">' +
						'<button type="button" class="logistica-planeacion__task-edit" data-action="planeacion-view" title="Ver actividad" aria-label="Ver actividad">' +
							'<span class="dashicons dashicons-visibility" aria-hidden="true"></span>' +
						'</button>' +
						'<button type="button" class="logistica-planeacion__task-edit" data-action="planeacion-edit" title="Editar" aria-label="Editar">' +
							'<span class="dashicons dashicons-edit" aria-hidden="true"></span>' +
						'</button>' +
				'</div>' +
					'<div class="logistica-planeacion__task-timeline-body">' +
						'<span class="logistica-planeacion__task-op">' + opText + '</span>' +
						descHtml +
							'</div>' +
				'</div>';

			return card;
		}

		function escapeHtml(text) {
			var div = document.createElement('div');
			div.textContent = text;
			return div.innerHTML;
		}

		function syncLunchControls() {
			if (includeLunchCheckbox) {
				includeLunchCheckbox.checked = !!state.include_lunch;
			}
			if (lunchStartInput) {
				lunchStartInput.value = minutesToTimeInput(state.lunch_start);
				lunchStartInput.disabled = !state.include_lunch;
			}
			if (lunchEndInput) {
				lunchEndInput.value = minutesToTimeInput(state.lunch_end);
				lunchEndInput.disabled = !state.include_lunch;
			}
		}

		function syncLunchTimesFromInputs() {
			if (lunchStartInput) {
				var start = timeToMinutes(lunchStartInput.value);
				if (Number.isFinite(start)) {
					state.lunch_start = start;
				}
			}
			if (lunchEndInput) {
				var end = timeToMinutes(lunchEndInput.value);
				if (Number.isFinite(end)) {
					state.lunch_end = end;
				}
			}
			var lunch = syncLunchTimesToModule(state.lunch_start, state.lunch_end);
			state.lunch_start = lunch.lunch_start;
			state.lunch_end = lunch.lunch_end;
		}

		function syncIncludeLunchCheckbox() {
			syncLunchControls();
		}

		function syncTaskColorMaps() {
			if (!window.LogisticaPlaneacionTaskColors) {
				return;
			}

			var weekTasks = {
				'0': state.tasks || []
			};

			if (state.prevWeekSchedule && Array.isArray(state.prevWeekSchedule.tasks)) {
				weekTasks['-1'] = state.prevWeekSchedule.tasks;
			}

			if (state.nextWeekSchedule && Array.isArray(state.nextWeekSchedule.tasks)) {
				weekTasks['1'] = state.nextWeekSchedule.tasks;
			}

			LogisticaPlaneacionTaskColors.syncWeekTaskMaps(weekTasks, compareGlobalTaskOrder);
		}

		function renderBoard() {
			hideTaskFloatTip();
			syncDayHoursFromInputs();
			syncLunchTimesFromInputs();
			scheduleIncludeLunch = !!state.include_lunch;
			syncLunchControls();
			releaseTasksFromReopenedDays(state.tasks, state.day_hours, state.days, state.calendarConfig);
			reconcileTasksWithClosedDays(state.tasks, state.day_hours, state.days, state.calendarConfig);
			runScheduleRecalculate();
			refreshAdjacentWeekPreviews();
			syncTaskColorMaps();
			updateDayColumnsState();

			renderAdjacentWeekStrips();

			board.querySelectorAll('[data-planeacion-day]:not(.logistica-planeacion__day--adjacent) [data-planeacion-dropzone]').forEach(function (zone) {
				renderTimelineZone(zone);
			});

			scheduleTaskCardTooltipSync();
			updateWeekMetricsPanel();
		}

		function updateWeekMetricsPanel() {
			if (!window.LogisticaPlaneacionMetrics || !metricsWeekPanel) {
				return;
			}

			var metrics = LogisticaPlaneacionMetrics.computeWeekMetrics({
				tasks: state.tasks,
				dayHours: state.day_hours,
				days: state.days,
				includeLunch: state.include_lunch,
				lunchStart: state.lunch_start,
				lunchEnd: state.lunch_end,
				isDayNonWorking: function (dayIndex) {
					return isDayNonWorking(dayIndex, state.day_hours, state.days, state.calendarConfig);
				},
				getDisplayItemsForDay: getDisplayItemsForDay
			});

			LogisticaPlaneacionMetrics.renderWeekPanel(metricsWeekPanel, metrics, state.statuses);
		}

		function renderCompareChartsFromCache() {
			if (!metricsRoot || !window.LogisticaPlaneacionMetrics || !pendingCompareMetrics) {
				return;
			}

			LogisticaPlaneacionMetrics.renderWeekComparisonCharts(
				metricsRoot,
				pendingCompareMetrics,
				state.statuses,
				window.Chart
			);
		}

		function loadWeekComparisonMetrics(forceReload) {
			if (!metricsRoot || !window.LogisticaPlaneacionMetrics) {
				return;
			}

			if (!state.semana_inicio) {
				return;
			}

			var cacheKey = state.machine + ':' + state.semana_inicio;
			if (!forceReload && cacheKey === compareMetricsCacheKey && pendingCompareMetrics) {
				renderCompareChartsFromCache();
				return;
			}

			compareMetricsCacheKey = cacheKey;

			var hint = metricsRoot.querySelector('[data-planeacion-metrics-compare-hint]')
				|| metricsRoot.querySelector('[data-planeacion-metrics-month-hint]');
			if (hint) {
				hint.textContent = 'Cargando comparaci?n de semanas?';
				hint.hidden = false;
			}

			ajax('logistica_get_planeacion_impresion_metrics', {
				semana_inicio: state.semana_inicio,
				maquina: state.machine
			}).then(function (response) {
				if (!response || !response.success) {
					if (hint) {
						hint.textContent = 'No se pudieron cargar las m?tricas de comparaci?n.';
						hint.hidden = false;
					}
					return;
				}

				pendingCompareMetrics = response.data;
				renderCompareChartsFromCache();
			});
		}

		function applySchedule(schedule) {
			var isNewWeek = !parseInt(schedule.id, 10);
			var templateDayHours = isNewWeek ? cloneDayHoursTemplate(state.day_hours) : null;

			state.semana_inicio = schedule.semana_inicio || state.semana_inicio;
			if (!state.days || !state.days.length) {
				state.days = buildDaysForWeekStart(state.semana_inicio);
			}
			state.include_lunch = normalizeIncludeLunch(schedule.include_lunch);
			scheduleIncludeLunch = state.include_lunch;
			var lunch = syncLunchTimesToModule(schedule.lunch_start, schedule.lunch_end);
			state.lunch_start = lunch.lunch_start;
			state.lunch_end = lunch.lunch_end;

			var dayHoursSource = schedule.day_hours;
			if (isNewWeek && templateDayHours) {
				dayHoursSource = inheritDayHoursForWeek(templateDayHours, state.days, state.calendarConfig);
			}

			state.day_hours = normalizeScheduleDayHours(
				dayHoursSource,
				parseInt(schedule.jornada_inicio, 10) || 420,
				parseInt(schedule.jornada_fin, 10) || 1020
			);
			state.tasks = Array.isArray(schedule.tasks) ? schedule.tasks.slice() : [];
			state.tasks.forEach(function (task) {
				if (Number.isFinite(Number(task.planned_start))) {
					return;
				}
				delete task.planned_start;
			});
			migrateSortOrderIfNeeded(state.tasks);
			syncIncludeLunchCheckbox();
			syncDayHoursToInputs();
			syncFormDaySelect();
			syncFormWeekSelect();
			updateWeekLabel();
			renderBoard();
		}

		function stripComputedTaskFields(task) {
			var clean = Object.assign({}, task);
			delete clean.schedule_segments;
			delete clean.continues_next_day;
			delete clean.continues_next_week;
			delete clean.exceeds_jornada;
			delete clean.schedule_overflow_unplaced;
			return clean;
		}

		function buildPayload() {
			syncDayHoursFromInputs();
			syncLunchTimesFromInputs();
			runScheduleRecalculate();
			var monday = getDayHours(state.day_hours, 0);
			return {
				jornada_inicio: monday.start,
				jornada_fin: monday.end,
				day_hours: state.day_hours,
				include_lunch: state.include_lunch ? 1 : 0,
				lunch_start: state.lunch_start,
				lunch_end: state.lunch_end,
				tasks: state.tasks.map(stripComputedTaskFields)
			};
		}

		function scheduleSave() {
			if (state.saveTimer) {
				window.clearTimeout(state.saveTimer);
			}

			setSaveStatus('Guardando?', 'is-saving');

			state.saveTimer = window.setTimeout(function () {
				saveSchedule();
			}, SAVE_DELAY);
		}

		function saveSchedule() {
			syncDayHoursFromInputs();
			runScheduleRecalculate();

			return ajax('logistica_save_planeacion_impresion', {
				semana_inicio: state.semana_inicio,
				maquina: state.machine,
				schedule_json: JSON.stringify(buildPayload())
			}).then(function (response) {
				if (!response || !response.success) {
					setSaveStatus((response && response.data && response.data.message) || 'Error al guardar', 'is-error');
			return;
		}

				if (response.data && response.data.schedule) {
					state.day_hours = normalizeScheduleDayHours(
						response.data.schedule.day_hours,
						parseInt(response.data.schedule.jornada_inicio, 10) || 420,
						parseInt(response.data.schedule.jornada_fin, 10) || 1020
					);
					syncDayHoursToInputs();
				}

				setSaveStatus('Guardado ' + formatPlaneacionTimeHms());
			}).catch(function () {
				setSaveStatus('Error de conexi?n', 'is-error');
			});
		}

		function loadSchedule() {
			setLoading(true);
			setSaveStatus('Cargando?', 'is-saving');

			var prevSemanaInicio = shiftWeek(state.semana_inicio, -1);
			var nextSemanaInicio = shiftWeek(state.semana_inicio, 1);

			return Promise.all([
				ajax('logistica_get_planeacion_impresion', {
				semana_inicio: state.semana_inicio,
				maquina: state.machine
				}),
				ajax('logistica_get_planeacion_impresion', {
					semana_inicio: prevSemanaInicio,
					maquina: state.machine
				}).catch(function () {
					return null;
				}),
				ajax('logistica_get_planeacion_impresion', {
					semana_inicio: nextSemanaInicio,
					maquina: state.machine
				}).catch(function () {
					return null;
				})
			]).then(function (results) {
				var response = results[0];
				var prevResponse = results[1];
				var nextResponse = results[2];
				setLoading(false);

				if (!response || !response.success) {
					var loadMessage = (response && response.data && response.data.message) || 'No se pudo cargar';
					setSaveStatus(loadMessage, 'is-error');
				return;
			}

				if (response.data.machines) {
					state.machines = response.data.machines;
					if (!state.machines[state.machine]) {
						state.machine = resolveStoredMachine(state.machines, 'sm74');
					}
					renderMachinesNav();
				}
				syncCustomMachineSlugs(response.data.custom_machine_slugs);

				if (response.data.field_options) {
					state.fieldOptions = loadFieldOptionsConfig({ field_options: response.data.field_options });
					syncFormDropdowns();
				}

				if (response.data.days) {
					state.days = response.data.days;
				} else {
					state.days = buildDaysForWeekStart(state.semana_inicio);
				}
				if (response.data.day_types) {
					state.calendarConfig.day_types = response.data.day_types;
				}
				if (response.data.holiday_settings) {
					state.calendarConfig.holiday_settings = response.data.holiday_settings;
				}
				if (response.data.festivo_dates) {
					state.calendarConfig.festivo_dates = Object.assign(
						{},
						state.calendarConfig.festivo_dates || {},
						response.data.festivo_dates
					);
				}
				if (response.data.labor_types) {
					state.laborTypes = response.data.labor_types;
					loadLaborConfig(response.data);
					loadPapelConfig(response.data);
				}

				state.carryoverTasks = [];
				state.prevWeekSchedule = null;
				state.prevWeekDays = [];
				state.prevSemanaInicio = '';
				state.nextWeekSchedule = null;
				state.nextWeekDays = [];
				state.nextSemanaInicio = '';
				state.previewTasksPrev = [];
				state.previewTasksNext = [];
				if (prevResponse && prevResponse.success) {
					var prevDays = prevResponse.data.days || buildDaysForWeekStart(prevSemanaInicio);
					state.prevWeekSchedule = prevResponse.data.schedule || null;
					state.prevWeekDays = prevDays;
					state.prevSemanaInicio = prevSemanaInicio;
				}

				if (nextResponse && nextResponse.success) {
					var nextDays = nextResponse.data.days || buildDaysForWeekStart(nextSemanaInicio);
					state.nextWeekSchedule = nextResponse.data.schedule || null;
					state.nextWeekDays = nextDays;
					state.nextSemanaInicio = nextSemanaInicio;
				}

				applySchedule(response.data.schedule || {});
				pendingCompareMetrics = null;
				setSaveStatus('');
			}).catch(function () {
				setLoading(false);
				setSaveStatus('Error de conexi?n', 'is-error');
			});
		}

		function taskIdMatches(task, id) {
			return task && String(task.id) === String(id);
		}

		function resolveTaskById(id) {
			if (id === null || id === undefined || id === '') {
				return null;
			}

			var fromTasks = state.tasks.find(function (task) {
				return taskIdMatches(task, id);
			});
			if (fromTasks) {
				return { task: fromTasks, source: 'tasks' };
			}

			var carryoverMatch = null;
			(state.carryoverTasks || []).some(function (entry, index) {
				if (!entry || !entry.task || !taskIdMatches(entry.task, id)) {
					return false;
				}
				carryoverMatch = { task: entry.task, source: 'carryover', carryoverIndex: index };
				return true;
			});

			if (carryoverMatch) {
				return carryoverMatch;
			}

			var previewPrev = (state.previewTasksPrev || []).find(function (task) {
				return taskIdMatches(task, id);
			});
			if (previewPrev) {
				return { task: previewPrev, source: 'preview-prev' };
			}

			var previewNext = (state.previewTasksNext || []).find(function (task) {
				return taskIdMatches(task, id);
			});
			if (previewNext) {
				return { task: previewNext, source: 'preview-next' };
			}

			return null;
		}

		function findTask(id) {
			var resolved = resolveTaskById(id);
			return resolved ? resolved.task : null;
		}

		function removeCarryoverTaskById(id) {
			state.carryoverTasks = (state.carryoverTasks || []).filter(function (entry) {
				return !(entry && entry.task && taskIdMatches(entry.task, id));
			});
		}

		function setModalViewOnly(viewOnly) {
			state.modalViewOnly = !!viewOnly;
			if (!form) {
				return;
			}

			form.classList.toggle('is-view-only', state.modalViewOnly);

			form.querySelectorAll('input, select, textarea, button').forEach(function (field) {
				if (field.matches('[data-action="planeacion-close-modal"]')) {
					return;
				}
				if (field.matches('[data-action="planeacion-delete-task"]')) {
					field.hidden = state.modalViewOnly || !state.editingId;
					field.disabled = state.modalViewOnly;
					return;
				}
				if (field.type === 'submit') {
					field.hidden = state.modalViewOnly;
					return;
				}
				if (field.matches('[data-planeacion-field], [data-planeacion-duration-input], [data-planeacion-task-status]')) {
					field.disabled = state.modalViewOnly;
				}
			});
		}

		function openModal(task, dayDefault, viewOnly, previewContext) {
			if (!modal || !form) {
				return;
			}

			state.editingId = task ? task.id : null;
			state.editingWeekOffset = 0;
			var isNew = !task;
			task = task || {
				id: generateId(),
				day: dayDefault !== undefined ? dayDefault : 0,
				labor_type: 'impresion',
				duration_minutes: 60,
				status: 'pending',
				op: '',
				labor: getLaborLabel('impresion'),
				tamanos: '',
				tintas: '',
				colores: '',
				tintas_config: defaultTintasConfig(),
				papel: '',
				papel_ancho: '',
				papel_largo: '',
				papel_gramaje: '',
				papel_material: '',
				papel_material_otro: '',
				montaje: '',
				prep_tinta: '',
				planchas: '',
				meta: '',
				complexity: 'normal',
				actual_start: null,
				actual_end: null,
				notes: ''
			};

			if (!task.labor_type) {
				task.labor_type = task.tamanos ? 'impresion' : 'otro';
			}

			if (task && task.id) {
				state.editingWeekOffset = resolveTaskSourceWeekOffset(task.id);
			}
			if (previewContext && previewContext.weekOffset != null) {
				state.editingWeekOffset = parseInt(previewContext.weekOffset, 10) || 0;
			}

			normalizePapelFields(task);

			syncFormDropdowns();
			syncFormWeekSelect();
			syncFormDaySelect();

			form.querySelectorAll('[data-planeacion-field]').forEach(function (field) {
				var key = field.getAttribute('data-planeacion-field');
				var value = task[key];

				if (field.type === 'checkbox') {
					field.checked = !!value;
				} else if (field.type === 'time') {
					field.value = normalizeTimeInputValue(timeToMinutes(value));
				} else if (field.tagName === 'SELECT') {
					if (value === null || value === undefined || value === '') {
						field.value = key === 'complexity' ? 'normal' : (key === 'labor_type' ? 'impresion' : '');
					} else {
						field.value = String(value);
					}
				} else {
					field.value = value !== null && value !== undefined ? value : '';
				}
			});

			applyTintasConfigToForm(ensureTintasConfig(task));
			bindTintasPanelEvents();

			updateFormLaborUI();
			updateFormPapelUI();
			updateFormDuration();

			var title = form.querySelector('[data-planeacion-modal-title]');
		if (title) {
				title.textContent = isNew
					? 'Nueva actividad'
					: (viewOnly ? 'Ver actividad' : 'Editar actividad');
			}

			var deleteBtn = form.querySelector('[data-action="planeacion-delete-task"]');
			if (deleteBtn) {
				deleteBtn.hidden = isNew;
		}

			var showPreview = !!viewOnly && !isNew;
			setModalPreviewMode(showPreview, task, previewContext);
			setModalViewOnly(showPreview);

		if (typeof modal.showModal === 'function') {
			modal.showModal();
		} else {
			modal.setAttribute('open', 'open');
		}
	}

	function closeModal() {
		if (!modal) {
			return;
		}

		if (typeof modal.close === 'function') {
			modal.close();
		} else {
			modal.removeAttribute('open');
		}

			state.editingId = null;
			setModalPreviewMode(false);
			setModalViewOnly(false);
		}

		function updateFormPapelUI() {
			if (!form) {
				return;
			}

			var materialField = form.querySelector('[data-planeacion-field="papel_material"]');
			var otroWrap = form.querySelector('[data-planeacion-papel-material-otro]');
			var otroInput = form.querySelector('[data-planeacion-field="papel_material_otro"]');
			var isOtro = materialField && materialField.value === 'otro';

			if (otroWrap) {
				otroWrap.hidden = !isOtro;
				otroWrap.classList.toggle('is-visible', isOtro);
			}

			if (otroInput) {
				otroInput.disabled = !isOtro;
				otroInput.required = isOtro;
				if (!isOtro) {
					otroInput.value = '';
				}
			}
		}

		function updateFormLaborUI() {
			if (!form) {
				return;
			}

			var laborTypeField = form.querySelector('[data-planeacion-field="labor_type"]');
			var laborField = form.querySelector('[data-planeacion-field="labor"]');
			var laborHint = form.querySelector('[data-planeacion-labor-hint]');
			var laborType = laborTypeField ? sanitizeLaborType(laborTypeField.value) : 'impresion';
			var rules = getLaborFieldRules(laborType);

			var tirajeSection = form.querySelector('[data-planeacion-labor-section="tiraje"]');
			if (tirajeSection) {
				tirajeSection.hidden = !laborFieldVisible(laborType, 'tamanos');
			}

			var tintasSection = form.querySelector('[data-planeacion-labor-section="tintas_colores"]');
			if (tintasSection) {
				tintasSection.hidden = !laborFieldVisible(laborType, 'tintas');
			}

			var papelSection = form.querySelector('[data-planeacion-labor-section="papel"]');
			if (papelSection) {
				papelSection.hidden = !laborFieldVisible(laborType, 'papel');
			}

			CHECKLIST_FIELDS.forEach(function (key) {
				var checklistLabel = form.querySelector('[data-planeacion-labor-field="' + key + '"]');
				if (checklistLabel) {
					checklistLabel.hidden = !laborFieldVisible(laborType, key);
				}
			});

			if (laborHint) {
				laborHint.textContent = rules.hint || '';
				laborHint.hidden = !rules.hint;
			}

			updateFormPapelUI();

			if (laborField && 'otro' !== laborType) {
				var current = String(laborField.value || '').trim();
				var labels = Object.keys(LABOR_TYPES).map(function (key) {
					return getLaborLabel(key);
				});
				if (!current || labels.indexOf(current) >= 0) {
					laborField.value = getLaborLabel(laborType);
				}
				laborField.placeholder = getLaborLabel(laborType);
			} else if (laborField) {
				laborField.placeholder = 'Describe la actividad';
			}
		}

		function updateFormDuration() {
			if (!form) {
				return;
			}

			var laborTypeField = form.querySelector('[data-planeacion-field="labor_type"]');
			var tamanosField = form.querySelector('[data-planeacion-field="tamanos"]');
			var complexityField = form.querySelector('[data-planeacion-field="complexity"]');
			var durationField = form.querySelector('[data-planeacion-duration-input]');
			var hint = form.querySelector('[data-planeacion-duration-hint]');
			var laborType = laborTypeField ? sanitizeLaborType(laborTypeField.value) : 'impresion';
			var autoDurationEnabled = usesAutoDurationFromTamanos(laborType);
			var count = tamanosField ? parseTamanos(tamanosField.value) : null;
			var complexity = complexityField ? complexityField.value : 'normal';
			var autoDuration = autoDurationEnabled ? durationFromTamanos(count, complexity) : null;
			var defaultDuration = getDefaultLaborMinutes(laborType);

			if (durationField) {
				if (autoDurationEnabled && autoDuration !== null) {
					durationField.readOnly = true;
					durationField.value = autoDuration;
				} else {
					durationField.readOnly = false;
					if (!durationField.value || parseInt(durationField.value, 10) <= 0) {
						durationField.value = defaultDuration;
					}
				}
			}

			if (hint) {
				if (autoDurationEnabled && count && autoDuration !== null) {
					var rateHours = count / TAMANOS_BASE;
					hint.textContent = count.toLocaleString('es-CO') + ' tama?os ? ' + formatDurationLabel(autoDuration) +
						' (' + rateHours.toFixed(1).replace('.0', '') + ' ? 60 min)';
					hint.hidden = false;
				} else if (!autoDurationEnabled) {
					hint.textContent = 'Duraci?n sugerida: ' + formatDurationLabel(defaultDuration) + '. Puedes ajustarla.';
					hint.hidden = false;
				} else {
					hint.textContent = 'Indica el tiraje para calcular la duraci?n autom?ticamente.';
					hint.hidden = false;
				}
			}
		}

		function readFormTask() {
			var task = {};

			form.querySelectorAll('[data-planeacion-field]').forEach(function (field) {
				var key = field.getAttribute('data-planeacion-field');
				if (field.type === 'checkbox') {
					task[key] = field.checked;
				} else if (field.type === 'time') {
					task[key] = timeToMinutes(field.value);
				} else if (key === 'duration_minutes' || key === 'day') {
					task[key] = parseInt(field.value, 10) || 0;
				} else if (key === 'week_offset') {
					task[key] = parseInt(field.value, 10) || 0;
				} else {
			task[key] = field.value;
				}
			});

			if (!task.id) {
				task.id = generateId();
			}

			normalizeTaskLabor(task);
			task.tintas_config = readTintasConfigFromForm();
			syncTintasSummariesOnTask(task);
			normalizePapelFields(task);
			applyDurationToTask(task);
		return task;
	}

		function saveFormTask() {
			var task = readFormTask();
			if (laborFieldVisible(task.labor_type, 'papel') && task.papel_material === 'otro' && !String(task.papel_material_otro || '').trim()) {
				updateFormPapelUI();
				var otroInput = form.querySelector('[data-planeacion-field="papel_material_otro"]');
				if (otroInput) {
					otroInput.focus();
				}
				window.alert('Indica el nombre del otro material.');
			return;
		}

			var targetWeekOffset = parseInt(task.week_offset, 10) || 0;
			var sourceWeekOffset = state.editingId
				? resolveTaskSourceWeekOffset(state.editingId)
				: 0;
			delete task.week_offset;

			if (targetWeekOffset !== sourceWeekOffset) {
				task.status_manual = true;
				applyStatusSideEffects(task, task.status || 'pending');
				if (!state.editingId && targetWeekOffset !== 0) {
					createTaskInWeekSchedule(task, targetWeekOffset).then(function (created) {
						if (created) {
							closeModal();
						}
					});
					return;
				}
				transferTaskBetweenWeeks(
					task.id,
					sourceWeekOffset,
					targetWeekOffset,
					task.day,
					0,
					task.planned_start
				).then(function (moved) {
					if (moved) {
						closeModal();
					}
				});
				return;
			}

			if (sourceWeekOffset !== 0) {
				task.status_manual = true;
				applyStatusSideEffects(task, task.status || 'pending');
				saveTaskToWeekSchedule(task, sourceWeekOffset).then(function (saved) {
					if (saved) {
						closeModal();
					}
				});
			return;
		}

			task.status_manual = true;
			applyStatusSideEffects(task, task.status || 'pending');
			var existingIndex = state.tasks.findIndex(function (item) {
				return taskIdMatches(item, task.id);
			});
			var carryoverSource = resolveTaskById(task.id);

			if (existingIndex >= 0) {
				var oldTask = state.tasks[existingIndex];
				var oldDay = parseInt(oldTask.day, 10);
				var newDay = parseInt(task.day, 10);
				applyDayChangeEffects(task, oldTask, newDay, oldDay);
				task.sort_order = oldTask.sort_order;
				if (task.planned_day === undefined || task.planned_day === null) {
					task.planned_day = oldTask.planned_day;
				}
				if (task.started_on_day === undefined) {
					task.started_on_day = oldTask.started_on_day;
				}
				state.tasks[existingIndex] = Object.assign({}, oldTask, task);
				if (newDay !== oldDay) {
					state.tasks = repositionTaskInGlobalOrder(state.tasks, task.id, newDay);
				}
			} else if (carryoverSource && carryoverSource.source === 'carryover') {
				var carryoverTask = carryoverSource.task;
				var carryOldDay = parseInt(carryoverTask.day, 10);
				var carryNewDay = parseInt(task.day, 10);
				applyDayChangeEffects(task, carryoverTask, carryNewDay, carryOldDay);
				task.sort_order = state.tasks.length;
				if (task.planned_day === undefined || task.planned_day === null) {
					task.planned_day = carryoverTask.planned_day != null ? carryoverTask.planned_day : carryNewDay;
				}
				if (task.started_on_day === undefined) {
					task.started_on_day = carryoverTask.started_on_day;
				}
				state.tasks.push(Object.assign({}, carryoverTask, task));
				removeCarryoverTaskById(task.id);
				state.tasks = repositionTaskInGlobalOrder(state.tasks, task.id, task.day);
			} else {
				task.sort_order = state.tasks.length;
				task.planned_day = parseInt(task.day, 10);
				state.tasks.push(task);
				state.tasks = repositionTaskInGlobalOrder(state.tasks, task.id, task.day);
			}

			syncDayHoursFromInputs();
			renderBoard();
			closeModal();
			scheduleSave();
		}

		function deleteTask(id) {
			var sourceWeekOffset = resolveTaskSourceWeekOffset(id);
			if (sourceWeekOffset !== 0) {
				deleteTaskFromWeekSchedule(id, sourceWeekOffset).then(function (deleted) {
					if (deleted) {
						closeModal();
					}
				});
				return;
			}

			state.tasks = state.tasks.filter(function (task) {
				return !taskIdMatches(task, id);
			});
			removeCarryoverTaskById(id);
			renderBoard();
			closeModal();
			scheduleSave();
		}

		function getPrimaryTasksForDayTimeline(dayNum) {
			var items = getDisplayItemsForDay(dayNum);
			var seen = {};
			var tasks = [];

			items.sort(function (a, b) {
				return (a.segment.start_minutes || 0) - (b.segment.start_minutes || 0);
			});

			items.forEach(function (item) {
				if (!item.isPrimary || item.isCarryover) {
			return;
		}
				if (seen[item.task.id]) {
					return;
				}
				seen[item.task.id] = true;
				tasks.push(item.task);
			});

			return tasks;
		}

		function getDayTimelineMetrics(dayNum, dayHours) {
			var hours = getDayHours(dayHours || state.day_hours, dayNum);
			return getTimelineMetrics(hours.start, hours.end);
		}

		function getTaskPrimaryStartOnDay(task, dayNum) {
			var items = getDisplayItemsForDay(dayNum);
			var start = null;

			items.forEach(function (item) {
				if (item.task.id !== task.id || !item.isPrimary) {
					return;
				}
				var value = Number(item.segment.start_minutes);
				if (Number.isFinite(value) && (start === null || value < start)) {
					start = value;
				}
			});

			if (start !== null) {
				return start;
			}

			if (Number.isFinite(Number(task.planned_start))) {
				return Number(task.planned_start);
			}

			return null;
		}

		function taskFitsAtStart(task, dayNum, start, excludeTaskId) {
			var metrics = getDayTimelineMetrics(dayNum);
			var duration = Number(task.duration_minutes) || 60;
			if (!Number.isFinite(start) || start < metrics.start || start + duration > metrics.end) {
				return false;
			}

			var items = getDisplayItemsForDay(dayNum);
			return !items.some(function (item) {
				if (!item.isPrimary || item.isCarryover) {
					return false;
				}
				if (excludeTaskId && item.task.id === excludeTaskId) {
					return false;
				}
				var blockStart = Number(item.segment.start_minutes);
				var blockEnd = Number(item.segment.end_minutes);
				if (!Number.isFinite(blockStart) || !Number.isFinite(blockEnd)) {
					return false;
				}
				return start < blockEnd && (start + duration) > blockStart;
			});
		}

		function findShiftStartForTask(task, dayNum, direction) {
			var metrics = getDayTimelineMetrics(dayNum);
			var duration = Number(task.duration_minutes) || 60;
			var currentStart = getTaskPrimaryStartOnDay(task, dayNum);
			if (!Number.isFinite(currentStart)) {
				currentStart = metrics.start;
			}

			var candidate = currentStart + (direction * 15);
			var guard = 0;

			while (guard < 200) {
				guard += 1;

				if (direction < 0 && candidate < metrics.start) {
					return null;
				}
				if (direction > 0 && candidate + duration > metrics.end) {
					return null;
				}

				candidate = skipLunch(candidate);

				if (taskFitsAtStart(task, dayNum, candidate, task.id)) {
					return candidate;
				}

				candidate += direction * 15;
			}

			return null;
		}

		function canShiftTaskOnTimeline(task, dayNum, direction) {
			return findShiftStartForTask(task, dayNum, direction) !== null;
		}

		function getTimelineMoveAvailability(taskId, columnDay) {
			var dayNum = parseInt(columnDay, 10);
			var task = findTask(taskId);
			var canMoveUp = false;
			var canMoveDown = false;

			if (!task || !Number.isFinite(dayNum)) {
				return { canMoveUp: canMoveUp, canMoveDown: canMoveDown };
			}

			if (isDayNonWorking(dayNum, state.day_hours, state.days, state.calendarConfig)) {
				return { canMoveUp: canMoveUp, canMoveDown: canMoveDown };
			}

			var dayTasks = getPrimaryTasksForDayTimeline(dayNum);
			var dayIndex = dayTasks.findIndex(function (item) {
				return item.id === taskId;
			});

			if (dayIndex > 0) {
				canMoveUp = true;
			} else if (canShiftTaskOnTimeline(task, dayNum, -1)) {
				canMoveUp = true;
			} else if (findAdjacentWorkingDay(dayNum, -1, state.day_hours, state.days, state.calendarConfig) !== null) {
				canMoveUp = true;
			}

			if (dayIndex >= 0 && dayIndex < dayTasks.length - 1) {
				canMoveDown = true;
			} else if (canShiftTaskOnTimeline(task, dayNum, 1)) {
				canMoveDown = true;
			} else if (findAdjacentWorkingDay(dayNum, 1, state.day_hours, state.days, state.calendarConfig) !== null) {
				canMoveDown = true;
			}

			return { canMoveUp: canMoveUp, canMoveDown: canMoveDown };
		}

		function swapTaskSortOrders(taskA, taskB) {
			var swapOrder = taskA.sort_order;
			taskA.sort_order = taskB.sort_order;
			taskB.sort_order = swapOrder;
			delete taskA.planned_start;
			delete taskB.planned_start;
			delete taskA.planned_day;
			delete taskB.planned_day;
		}

		function finishTimelineMove() {
			state.tasks = normalizeGlobalSortOrders(state.tasks.slice());
			syncDayHoursFromInputs();
			runScheduleRecalculate();
			renderBoard();
			scheduleSave();
		}

		function shiftTaskOnTimeline(task, dayNum, direction) {
			var targetStart = findShiftStartForTask(task, dayNum, direction);
			if (!Number.isFinite(targetStart)) {
				return false;
			}

			task.planned_start = targetStart;
			task.day = dayNum;
			delete task.planned_day;
			return true;
		}

		function computePlannedStartForDaySlot(dayNum, insertIndex, excludeTaskId, taskDuration) {
			var metrics = getDayTimelineMetrics(dayNum);
			var items = getDisplayItemsForDay(String(dayNum)).filter(function (item) {
				return item.isPrimary && !item.isCarryover && item.task.id !== excludeTaskId;
			});

			items.sort(function (a, b) {
				return (a.segment.start_minutes || 0) - (b.segment.start_minutes || 0);
			});

			if (!items.length || insertIndex <= 0) {
				return skipLunch(metrics.start);
			}

			if (insertIndex >= items.length) {
				var lastEnd = metrics.start;
				items.forEach(function (item) {
					var end = Number(item.segment.end_minutes);
					if (Number.isFinite(end) && end > lastEnd) {
						lastEnd = end;
					}
				});
				return skipLunch(Math.max(metrics.start, Math.min(lastEnd, metrics.end - 15)));
			}

			taskDuration = Number(taskDuration) || 60;
			var targetStart = Number(items[insertIndex].segment.start_minutes);
			if (!Number.isFinite(targetStart)) {
				return skipLunch(metrics.start);
			}

			var candidate = targetStart - taskDuration;
			if (candidate < metrics.start) {
				candidate = metrics.start;
			}

			return skipLunch(candidate);
		}

		function moveTaskToAdjacentDay(id, columnDay, direction, sourceWeekOffset) {
			sourceWeekOffset = parseInt(sourceWeekOffset, 10) || 0;
			var boundary = findCrossWeekBoundaryDay(columnDay, direction, sourceWeekOffset);
			if (!boundary) {
				return false;
			}

			var task = findTaskForWeekOffset(id, sourceWeekOffset);
			if (!task) {
				return false;
			}

			var zoneCtx = getWeekContextByOffset(boundary.weekOffset);
			var dayTasks = getDisplayItemsForDay(boundary.day, {
				displayWeekOffset: boundary.weekOffset,
				days: zoneCtx.days,
				dayHours: zoneCtx.dayHours,
				includeCarryover: boundary.weekOffset === 0,
				taskSource: getTaskSourcesForWeekOffset(boundary.weekOffset)
			}).filter(function (item) {
				return item.isPrimary && !item.isCarryover && item.task.id !== id;
			}).map(function (item) {
				return item.task;
			});
			var insertIndex = direction > 0 ? 0 : dayTasks.length;

			if (boundary.crossWeek) {
				transferTaskBetweenWeeks(
					id,
					sourceWeekOffset,
					boundary.weekOffset,
					boundary.day,
					insertIndex,
					null
				);
				return true;
			}

			var oldDay = parseInt(task.day, 10);
			var oldTaskSnapshot = Object.assign({}, task);

			if (sourceWeekOffset === 0) {
				applyDayChangeEffects(task, oldTaskSnapshot, boundary.day, oldDay);
				task.day = boundary.day;
				delete task.planned_day;
				task.planned_start = computePlannedStartForDaySlot(
					boundary.day,
					insertIndex,
					id,
					task.duration_minutes
				);

				var others = state.tasks.filter(function (item) {
					return item.id !== id;
				}).sort(compareGlobalTaskOrder);

				others.splice(computeGlobalInsertIndex(others, boundary.day, insertIndex), 0, task);
				state.tasks = assignSequentialSortOrders(others);
				finishTimelineMove();
				return true;
			}

			transferTaskBetweenWeeks(
				id,
				sourceWeekOffset,
				sourceWeekOffset,
				boundary.day,
				insertIndex,
				null
			);
			return true;
		}

		function reorderTask(id, targetDay, targetIndex) {
			var task = findTask(id);
			if (!task) {
			return;
		}

			var oldDay = parseInt(task.day, 10);
			var oldTaskSnapshot = Object.assign({}, task);
			applyDayChangeEffects(task, oldTaskSnapshot, targetDay, oldDay);
			task.day = targetDay;
			if (parseInt(targetDay, 10) !== oldDay) {
				delete task.planned_start;
			}
			if (!isDayNonWorking(parseInt(targetDay, 10), state.day_hours, state.days, state.calendarConfig)) {
				delete task.planned_day;
			}

			var others = state.tasks.filter(function (item) {
				return item.id !== id;
			}).sort(compareGlobalTaskOrder);

			others.splice(computeGlobalInsertIndex(others, targetDay, targetIndex), 0, task);
			delete task.planned_day;
			state.tasks = assignSequentialSortOrders(others);
			renderBoard();
			scheduleSave();
		}

		function moveTask(id, direction, columnDay, sourceWeekOffset) {
			var task = findTaskForWeekOffset(id, sourceWeekOffset || 0);
			if (!task) {
				return;
			}

			sourceWeekOffset = parseInt(sourceWeekOffset, 10) || resolveTaskSourceWeekOffset(id);
			var zoneCtx = getWeekContextByOffset(sourceWeekOffset);
			var dayNum = parseInt(columnDay, 10);
			if (!Number.isFinite(dayNum)) {
				dayNum = parseInt(task.day, 10);
			}

			if (isDayNonWorking(dayNum, zoneCtx.dayHours, zoneCtx.days, state.calendarConfig)) {
				return;
			}

			var dayTasks = getDisplayItemsForDay(dayNum, {
				displayWeekOffset: sourceWeekOffset,
				days: zoneCtx.days,
				dayHours: zoneCtx.dayHours,
				includeCarryover: sourceWeekOffset === 0,
				taskSource: getTaskSourcesForWeekOffset(sourceWeekOffset)
			}).filter(function (item) {
				return item.isPrimary && !item.isCarryover;
			}).map(function (item) {
				return item.task;
			});
			var dayIndex = dayTasks.findIndex(function (item) {
				return item.id === id;
			});

			if (dayIndex >= 0) {
				var neighborIndex = dayIndex + direction;
				if (neighborIndex >= 0 && neighborIndex < dayTasks.length) {
					if (sourceWeekOffset === 0) {
						swapTaskSortOrders(task, dayTasks[neighborIndex]);
						finishTimelineMove();
					} else {
						transferTaskBetweenWeeks(
							id,
							sourceWeekOffset,
							sourceWeekOffset,
							dayNum,
							neighborIndex,
							null
						);
					}
				return;
			}

				if (moveTaskToAdjacentDay(id, dayNum, direction, sourceWeekOffset)) {
				return;
				}
			}

			if (sourceWeekOffset === 0 && shiftTaskOnTimeline(task, dayNum, direction)) {
				finishTimelineMove();
				return;
			}

			moveTaskToAdjacentDay(id, dayNum, direction, sourceWeekOffset);
		}

		function minutesFromTimelineDrop(zone, clientX, dayNum, dayHours) {
			var track = zone.querySelector('[data-planeacion-timeline-track]');
			if (!track) {
				return null;
			}

			var rect = track.getBoundingClientRect();
			if (!rect.width) {
				return null;
			}

			var metrics = getDayTimelineMetrics(dayNum, dayHours || state.day_hours);
			var pct = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
			var minutes = metrics.start + (pct * metrics.span);
			minutes = Math.round(minutes / 15) * 15;
			minutes = skipLunch(Math.max(metrics.start, Math.min(metrics.end - 15, minutes)));
			return minutes;
		}

		function resolveTimelineDropInsertIndex(cards, clientX) {
			if (!cards.length) {
				return 0;
			}

			var sorted = cards.slice().sort(function (a, b) {
				return a.getBoundingClientRect().left - b.getBoundingClientRect().left;
			});

			for (var i = 0; i < sorted.length; i++) {
				var rect = sorted[i].getBoundingClientRect();
				if (clientX < rect.left + (rect.width / 2)) {
					return cards.indexOf(sorted[i]);
				}
			}

			return cards.length;
		}

		function setMachine(slug) {
			if (!state.machines[slug] || slug === state.machine) {
				return;
			}

			state.machine = slug;
			try {
				window.localStorage.setItem('logistica_planeacion_machine', slug);
			} catch (error) {
				// ignore
			}

			renderMachinesNav();
			loadSchedule();
		}

		function clearDragDropIndicator() {
			board.querySelectorAll('[data-planeacion-drop-indicator]').forEach(function (node) {
				node.remove();
			});
		}

		function updateDragDropIndicator(zone, clientX, dragId) {
			clearDragDropIndicator();

			if (!zone || !dragId) {
				return;
			}

			var track = zone.querySelector('[data-planeacion-timeline-track]');
			if (!track) {
				return;
			}

			var zoneCtx = getZonePlanningContext(zone);
			var day = parseInt(zone.getAttribute('data-planeacion-dropzone'), 10);
			if (!Number.isFinite(day) || isDayNonWorking(day, zoneCtx.dayHours, zoneCtx.days, state.calendarConfig)) {
				return;
			}

			var task = findTaskForWeekOffset(dragId, resolveTaskSourceWeekOffset(dragId));
			if (!task) {
				return;
			}

			var snappedStart = minutesFromTimelineDrop(zone, clientX, day, zoneCtx.dayHours);
			if (!Number.isFinite(snappedStart)) {
				return;
			}

			var metrics = getDayTimelineMetrics(day, zoneCtx.dayHours);
			var duration = Number(task.duration_minutes) || 60;
			var snappedEnd = snappedStart + duration;

			if (snappedEnd > metrics.end) {
				snappedStart = Math.max(metrics.start, metrics.end - duration);
				snappedEnd = snappedStart + duration;
			}

			var linePos = rangeToTimelinePercent(snappedStart, snappedStart, metrics);
			var ghostPos = rangeToTimelinePercent(snappedStart, snappedEnd, metrics);
			var fits = taskFitsAtStart(task, day, snappedStart, dragId);

			var indicator = document.createElement('div');
			indicator.className = 'logistica-planeacion__timeline-drop-indicator' + (fits ? '' : ' is-invalid');
			indicator.setAttribute('data-planeacion-drop-indicator', '');

			var ghost = document.createElement('div');
			ghost.className = 'logistica-planeacion__timeline-drop-indicator-ghost';
			ghost.style.left = ghostPos.left + '%';
			ghost.style.width = ghostPos.width + '%';

			var line = document.createElement('div');
			line.className = 'logistica-planeacion__timeline-drop-indicator-line';
			line.style.left = linePos.left + '%';

			var label = document.createElement('span');
			label.className = 'logistica-planeacion__timeline-drop-indicator-label';
			label.textContent = formatRange(snappedStart, snappedEnd);

			ghost.appendChild(label);
			indicator.appendChild(ghost);
			indicator.appendChild(line);
			track.appendChild(indicator);
		}

		function handleTimelineDrop(event, zone) {
			event.preventDefault();
			zone.classList.remove('is-drag-over');
			clearDragDropIndicator();

			var id = state.dragId || event.dataTransfer.getData('text/plain');
			if (!id) {
				return;
			}

			var zoneCtx = getZonePlanningContext(zone);
			var targetWeekOffset = zoneCtx.weekOffset;
			var sourceWeekOffset = resolveTaskSourceWeekOffset(id);
			var day = parseInt(zone.getAttribute('data-planeacion-dropzone'), 10);
			if (isDayNonWorking(day, zoneCtx.dayHours, zoneCtx.days, state.calendarConfig)) {
				return;
			}

			var cards = Array.from(zone.querySelectorAll('[data-task-id]')).filter(function (node) {
				return node.dataset.taskId !== id && node.dataset.segmentContinuation !== '1';
			});

			var track = zone.querySelector('[data-planeacion-timeline-track]');
			var insertIndex = track
				? resolveTimelineDropInsertIndex(cards, event.clientX)
				: cards.length;
			if (!track) {
				var afterElement = document.elementFromPoint(event.clientX, event.clientY);
				if (afterElement) {
					var overCard = afterElement.closest('[data-task-id]');
					if (overCard && overCard.parentElement === zone) {
						insertIndex = cards.indexOf(overCard);
						if (insertIndex < 0) {
							insertIndex = cards.length;
						}
					}
				}
			}

			var snappedStart = track ? minutesFromTimelineDrop(zone, event.clientX, day, zoneCtx.dayHours) : null;

			if (sourceWeekOffset !== targetWeekOffset) {
				transferTaskBetweenWeeks(
					id,
					sourceWeekOffset,
					targetWeekOffset,
					day,
					insertIndex,
					snappedStart
				);
				return;
			}

			if (sourceWeekOffset === 0) {
				var droppedTask = findTask(id);
				reorderTask(id, day, insertIndex);

				if (droppedTask && snappedStart !== null && taskFitsAtStart(droppedTask, day, snappedStart, id)) {
					droppedTask.planned_start = snappedStart;
					finishTimelineMove();
				}
				return;
			}

			transferTaskBetweenWeeks(
				id,
				sourceWeekOffset,
				sourceWeekOffset,
				day,
				insertIndex,
				snappedStart
			);
		}

		function bindDragDrop() {
			board.addEventListener('dragover', function (event) {
				if (!state.dragId) {
					return;
				}

				var zone = event.target.closest('[data-planeacion-dropzone]');
				if (zone) {
					event.preventDefault();
					zone.classList.add('is-drag-over');
					updateDragDropIndicator(zone, event.clientX, state.dragId);
				}
			});

			board.addEventListener('dragstart', function (event) {
				if (event.target.closest('button, select')) {
					event.preventDefault();
					return;
				}

				var card = event.target.closest('[data-task-id]');
				if (!card || card.dataset.segmentContinuation === '1') {
					event.preventDefault();
			return;
		}

				state.dragId = card.dataset.taskId;
				card.classList.add('is-dragging');
				event.dataTransfer.effectAllowed = 'move';
				event.dataTransfer.setData('text/plain', state.dragId);
			});

			board.addEventListener('dragend', function (event) {
				var card = event.target.closest('[data-task-id]');
				if (card) {
					card.classList.remove('is-dragging');
				}
				state.dragId = null;
				clearDragDropIndicator();
				board.querySelectorAll('.is-drag-over').forEach(function (node) {
					node.classList.remove('is-drag-over');
				});
			});

			board.addEventListener('dragleave', function (event) {
				var zone = event.target.closest('[data-planeacion-dropzone]');
				if (!zone) {
					return;
				}
				var related = event.relatedTarget;
				if (related && zone.contains(related)) {
					return;
				}
					zone.classList.remove('is-drag-over');
				clearDragDropIndicator();
			});

			board.addEventListener('drop', function (event) {
				var zone = event.target.closest('[data-planeacion-dropzone]');
				if (!zone) {
			return;
		}
				handleTimelineDrop(event, zone);
			});
	}

		bindTaskFloatTipEvents();

		if (metricsRoot) {
			var compareDetails = metricsRoot.querySelector('.logistica-planeacion__metrics-compare')
				|| metricsRoot.querySelector('.logistica-planeacion__metrics-month');
			if (compareDetails) {
				compareDetails.addEventListener('toggle', function () {
					if (!compareDetails.open) {
						return;
					}
					if (pendingCompareMetrics) {
						renderCompareChartsFromCache();
						return;
					}
					loadWeekComparisonMetrics(false);
				});
			}
	}

	board.addEventListener('click', function (event) {
			if (event.target.closest('[data-action="planeacion-edit-machine"]')) {
				var editBtn = event.target.closest('[data-action="planeacion-edit-machine"]');
				openMachineModal(editBtn.getAttribute('data-planeacion-machine'));
			return;
		}

			var machineBtn = event.target.closest('[data-planeacion-machine]');
			if (machineBtn && !machineBtn.classList.contains('logistica-planeacion__machine-btn--add') && !machineBtn.classList.contains('logistica-planeacion__machine-edit')) {
				setMachine(machineBtn.getAttribute('data-planeacion-machine'));
				return;
			}

			var actionNode = event.target.closest('[data-action]');
			if (!actionNode) {
				return;
			}

			var action = actionNode.getAttribute('data-action');

			if (action === 'planeacion-add-machine') {
				openMachineModal();
				return;
			}

			if (action === 'planeacion-delete-machine') {
				var deleteSlug = machineSlugInput ? machineSlugInput.value : '';
				if (deleteSlug) {
					deleteMachine(deleteSlug);
				}
				return;
			}

			if (action === 'planeacion-close-machine-modal') {
				closeMachineModal();
				return;
			}

		if (action === 'planeacion-prev-week') {
				state.semana_inicio = shiftWeek(state.semana_inicio, -1);
				loadSchedule();
			return;
		}

		if (action === 'planeacion-next-week') {
				state.semana_inicio = shiftWeek(state.semana_inicio, 1);
				loadSchedule();
			return;
		}

		if (action === 'planeacion-open-overflow-week') {
				var weekOffset = parseInt(actionNode.getAttribute('data-week-offset'), 10) || 1;
				state.semana_inicio = shiftWeek(state.semana_inicio, weekOffset);
				loadSchedule();
			return;
		}

		if (action === 'planeacion-open-prior-week') {
				state.semana_inicio = shiftWeek(state.semana_inicio, -1);
				loadSchedule();
			return;
		}

		if (action === 'planeacion-toggle-adjacent') {
				var adjacentKey = actionNode.getAttribute('data-adjacent');
				if (adjacentKey === 'prev' || adjacentKey === 'next') {
					toggleAdjacentWeekStrip(adjacentKey);
				}
			return;
		}

		if (action === 'planeacion-today-week') {
				state.semana_inicio = weekStartFromDate('');
				loadSchedule();
			return;
		}

		if (action === 'planeacion-pedidos-open') {
				if (window.LogisticaPlaneacionPedidosBridge && typeof window.LogisticaPlaneacionPedidosBridge.open === 'function') {
					window.LogisticaPlaneacionPedidosBridge.open('impresion');
				}
			return;
		}

		if (action === 'planeacion-add-task') {
				openModal(null, 0);
			return;
		}

		if (action === 'planeacion-add-task-day') {
				var dayIndex = parseInt(actionNode.getAttribute('data-day'), 10) || 0;
				if (isDayNonWorking(dayIndex, state.day_hours, state.days, state.calendarConfig)) {
			return;
		}
				openModal(null, dayIndex);
			return;
		}

			if (action === 'planeacion-edit-field-options') {
				openOptionsModal();
			return;
		}

			if (action === 'planeacion-export-excel') {
				exportPlaneacionExcel();
			return;
		}

			if (action === 'planeacion-import-excel') {
				var importInput = board.querySelector('[data-planeacion-import-file]');
				if (importInput) {
					importInput.click();
				}
			return;
		}

			if (action === 'planeacion-close-modal') {
				closeModal();
			return;
		}

			if (action === 'planeacion-delete-task' && state.editingId) {
				deleteTask(state.editingId);
			return;
		}

			var taskCard = event.target.closest('[data-task-id]');
			if (!taskCard) {
				return;
			}

			var taskId = taskCard.dataset.taskId;

			if (action === 'planeacion-view') {
				var viewTask = findTask(taskId);
				openModal(viewTask, undefined, true, readTaskCardPreviewContext(taskCard, viewTask));
			} else if (action === 'planeacion-edit') {
				openModal(findTask(taskId));
			} else if (action === 'planeacion-move-up') {
				if (actionNode.classList.contains('is-disabled')) {
					return;
				}
				event.preventDefault();
				event.stopPropagation();
				moveTask(taskId, -1, taskCard.dataset.columnDay, taskCard.dataset.weekOffset);
			} else if (action === 'planeacion-move-down') {
				if (actionNode.classList.contains('is-disabled')) {
					return;
				}
				event.preventDefault();
				event.stopPropagation();
				moveTask(taskId, 1, taskCard.dataset.columnDay, taskCard.dataset.weekOffset);
			}
		});

		board.addEventListener('change', function (event) {
			var importInput = event.target.closest('[data-planeacion-import-file]');
			if (importInput && importInput.files && importInput.files[0]) {
				importPlaneacionExcel(importInput.files[0]).finally(function () {
					importInput.value = '';
				});
				return;
			}

			var select = event.target.closest('[data-planeacion-task-status]');
			if (select) {
				var card = select.closest('[data-task-id]');
				if (card) {
					setTaskStatus(card.dataset.taskId, select.value);
				}
				return;
			}

			var input = event.target;
			if (input.matches('[data-planeacion-include-lunch]')) {
				state.include_lunch = input.checked;
				scheduleIncludeLunch = state.include_lunch;
				syncLunchControls();
				renderBoard();
				scheduleSave();
				return;
			}
			if (input.matches('[data-planeacion-lunch-start], [data-planeacion-lunch-end]')) {
				syncLunchTimesFromInputs();
				renderBoard();
				scheduleSave();
				return;
			}
			if (input.matches('[data-planeacion-day-type]')) {
				applyDayTypeSelection(input.getAttribute('data-planeacion-day-type'), input.value);
				renderBoard();
				scheduleSave();
				return;
			}
			if (!input.matches('[data-planeacion-day-start], [data-planeacion-day-end]')) {
				return;
			}
			renderBoard();
			scheduleSave();
		});

		if (machineForm) {
			machineForm.addEventListener('submit', function (event) {
				event.preventDefault();
				var nameInput = machineForm.querySelector('[data-planeacion-machine-name]');
				var name = nameInput ? String(nameInput.value || '').trim() : '';
				var slug = machineSlugInput ? String(machineSlugInput.value || '').trim() : '';
				if (!name) {
					if (nameInput) {
						nameInput.focus();
					}
				return;
				}
				if (slug) {
					updateMachine(slug, name);
					return;
				}
				addMachine(name);
			});
		}

		bindModalActions(modal, {
			'planeacion-close-modal': function () {
				closeModal();
			},
			'planeacion-delete-task': function () {
				if (state.editingId) {
					deleteTask(state.editingId);
				}
			}
		});

		bindModalActions(machineModal, {
			'planeacion-close-machine-modal': function () {
				closeMachineModal();
			},
			'planeacion-delete-machine': function () {
				var deleteSlug = machineSlugInput ? machineSlugInput.value : '';
				if (deleteSlug) {
					deleteMachine(deleteSlug);
				}
			}
		});

		bindModalActions(optionsModal, {
			'planeacion-close-options-modal': function () {
				closeOptionsModal();
			}
		});

		if (optionsForm) {
			optionsForm.addEventListener('submit', function (event) {
			event.preventDefault();
				saveFieldOptions();
			});
		}

		renderMachinesNav();
		bindDragDrop();

		if (form) {
			form.addEventListener('submit', function (event) {
				event.preventDefault();
				if (state.modalViewOnly) {
					closeModal();
					return;
				}
				saveFormTask();
			});

			form.addEventListener('input', function (event) {
				var field = event.target;
				if (field.matches('[data-planeacion-field="papel_material"]')) {
					updateFormPapelUI();
					return;
				}
				if (field.matches('[data-planeacion-field="labor_type"]')) {
					stripHiddenLaborFormFields(sanitizeLaborType(field.value));
					updateFormLaborUI();
					var durationField = form.querySelector('[data-planeacion-duration-input]');
					if (durationField) {
						durationField.value = getDefaultLaborMinutes(field.value);
					}
				}
				if (!field.matches('[data-planeacion-field="tamanos"], [data-planeacion-field="complexity"], [data-planeacion-field="labor_type"], [data-planeacion-duration-input]')) {
					return;
				}
				updateFormDuration();
			});

			form.addEventListener('change', function (event) {
				var field = event.target;
				if (field.matches('[data-planeacion-field="papel_material"]')) {
					updateFormPapelUI();
					return;
				}
				if (!field.matches('[data-planeacion-field="complexity"], [data-planeacion-field="labor_type"]')) {
					return;
				}
				if (field.matches('[data-planeacion-field="labor_type"]')) {
					stripHiddenLaborFormFields(sanitizeLaborType(field.value));
					updateFormLaborUI();
					var durationField = form.querySelector('[data-planeacion-duration-input]');
					if (durationField) {
						durationField.value = getDefaultLaborMinutes(field.value);
					}
				}
				updateFormDuration();
			});
		}

		updateWeekLabel();
		syncFormDropdowns();

		var tooltipResizeTimer = null;
		window.addEventListener('resize', function () {
			if (tooltipResizeTimer) {
				window.clearTimeout(tooltipResizeTimer);
			}
			tooltipResizeTimer = window.setTimeout(scheduleTaskCardTooltipSync, 120);
		});

		if (window.LogisticaPlaneacionPedidosBridge) {
			window.LogisticaPlaneacionPedidosBridge.init({
				module: 'impresion',
				board: board,
				root: root,
				nonceKey: 'planeacionNonce',
				getContext: function () {
					return {
						semana_inicio: state.semana_inicio,
						maquina: state.machine
					};
				},
				applySchedule: function (schedule) {
					applySchedule(schedule);
				},
				onScheduled: function (data) {
					if (data && data.message) {
						setSaveStatus(data.message);
					}
				}
			});
		}

	loadSchedule();
	}

	if (document.readyState === 'loading') {
		document.addEventListener('DOMContentLoaded', initPlaneacionImpresion);
	} else {
		initPlaneacionImpresion();
	}

	document.addEventListener('logistica:tab-loaded', function (event) {
		var tab = event && event.detail ? event.detail.tab : '';
		if (!tab || tab === 'logistica-planeacion-impresion') {
			initPlaneacionImpresion();
		}
	});

	window.LogisticaPlaneacionImpresion = {
		init: initPlaneacionImpresion
	};
}());
