<?php
/**
 * Database helpers for the Logistica plugin.
 *
 * @package Logistica
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Centralizes custom table names and schema creation.
 */
class Logistica_Database {
	/**
	 * Returns a custom table name using the active WordPress prefix.
	 *
	 * @param string $key Short table key.
	 * @return string
	 */
	public static function table( $key ) {
		global $wpdb;

		$tables = array(
			'productos'     => $wpdb->prefix . 'latingraf_productos',
			'materia_prima' => $wpdb->prefix . 'latingraf_materia_prima',
			'movimientos'   => $wpdb->prefix . 'latingraf_movimientos',
			'conversiones'  => $wpdb->prefix . 'latingraf_conversiones',
			'empaques'      => $wpdb->prefix . 'latingraf_empaques',
			'reportes'          => $wpdb->prefix . 'latingraf_reportes',
			'inventario_bodega'      => $wpdb->prefix . 'latingraf_inventario_bodega',
			'inventario_ajustes'     => $wpdb->prefix . 'latingraf_inventario_ajustes',
			'cierre_caracteristicas' => $wpdb->prefix . 'latingraf_cierre_caracteristicas',
			'etiqueta_productos'     => $wpdb->prefix . 'latingraf_etiqueta_productos',
			'etiqueta_pack_historial' => $wpdb->prefix . 'latingraf_etiqueta_pack_historial',
			'rotulo_rollos'          => $wpdb->prefix . 'latingraf_rotulo_rollos',
			'rotulo_rollos_print_historial' => $wpdb->prefix . 'latingraf_rotulo_rollos_print_historial',
			'planeacion_impresion'   => $wpdb->prefix . 'latingraf_planeacion_impresion',
			'planeacion_formado'     => $wpdb->prefix . 'latingraf_planeacion_formado',
			'pedidos_inventario'     => $wpdb->prefix . 'latingraf_pedidos_inventario',
			'pedidos_entregas'       => $wpdb->prefix . 'latingraf_pedidos_entregas',
			'pedidos_ordenes_compra' => $wpdb->prefix . 'latingraf_pedidos_ordenes_compra',
			'pedidos_ordenes_lineas' => $wpdb->prefix . 'latingraf_pedidos_ordenes_lineas',
			'pedidos_orden_linea_entregas' => $wpdb->prefix . 'latingraf_pedidos_orden_linea_entregas',
			'pedidos_clientes'       => $wpdb->prefix . 'latingraf_pedidos_clientes',
			'minutas'                => $wpdb->prefix . 'latingraf_minutas',
			'auditoria'              => $wpdb->prefix . 'latingraf_auditoria',
			'papelera'               => $wpdb->prefix . 'latingraf_papelera',
			'versiones'              => $wpdb->prefix . 'latingraf_versiones',
		);

		return isset( $tables[ $key ] ) ? $tables[ $key ] : '';
	}

	/**
	 * Creates or updates the plugin tables.
	 *
	 * @return void
	 */
	public static function create_tables() {
		global $wpdb;

		if ( ! function_exists( 'dbDelta' ) ) {
			require_once ABSPATH . 'wp-admin/includes/upgrade.php';
		}

		$charset_collate = $wpdb->get_charset_collate();

		$sql = array();

		$sql[] = 'CREATE TABLE ' . self::table( 'productos' ) . " (
			id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			producto varchar(190) NOT NULL,
			referencia varchar(120) NOT NULL DEFAULT '',
			tipo_producto varchar(120) NOT NULL DEFAULT '',
			medida_onzas decimal(10,2) NOT NULL DEFAULT 0,
			unidad_empaque varchar(80) NOT NULL DEFAULT '',
			cantidad_por_caja int(11) NOT NULL DEFAULT 0,
			numero_cajas int(11) NOT NULL DEFAULT 0,
			saldos_sueltos int(11) NOT NULL DEFAULT 0,
			cantidad_total int(11) NOT NULL DEFAULT 0,
			ubicacion varchar(120) NOT NULL DEFAULT '',
			estado varchar(60) NOT NULL DEFAULT 'activo',
			observaciones text NULL,
			created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
			updated_at datetime NULL,
			PRIMARY KEY  (id),
			KEY referencia (referencia),
			KEY estado (estado)
		) $charset_collate;";

		$sql[] = 'CREATE TABLE ' . self::table( 'materia_prima' ) . " (
			id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			tipo_material varchar(160) NOT NULL,
			proveedor varchar(160) NOT NULL DEFAULT '',
			gramaje varchar(80) NOT NULL DEFAULT '',
			calibre varchar(80) NOT NULL DEFAULT '',
			medida varchar(120) NOT NULL DEFAULT '',
			peso_kg decimal(12,3) NOT NULL DEFAULT 0,
			cantidad_hojas int(11) NOT NULL DEFAULT 0,
			color varchar(80) NOT NULL DEFAULT '',
			uso_relacionado varchar(190) NOT NULL DEFAULT '',
			observaciones text NULL,
			created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
			updated_at datetime NULL,
			PRIMARY KEY  (id),
			KEY tipo_material (tipo_material),
			KEY proveedor (proveedor)
		) $charset_collate;";

		$sql[] = 'CREATE TABLE ' . self::table( 'movimientos' ) . " (
			id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			tipo_movimiento varchar(80) NOT NULL,
			origen varchar(80) NOT NULL DEFAULT '',
			item_id bigint(20) unsigned NOT NULL DEFAULT 0,
			descripcion varchar(190) NOT NULL DEFAULT '',
			cantidad decimal(12,3) NOT NULL DEFAULT 0,
			unidad varchar(40) NOT NULL DEFAULT '',
			ubicacion_origen varchar(120) NOT NULL DEFAULT '',
			ubicacion_destino varchar(120) NOT NULL DEFAULT '',
			observaciones text NULL,
			created_by bigint(20) unsigned NOT NULL DEFAULT 0,
			created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
			PRIMARY KEY  (id),
			KEY tipo_movimiento (tipo_movimiento),
			KEY item_id (item_id)
		) $charset_collate;";

		$sql[] = 'CREATE TABLE ' . self::table( 'conversiones' ) . " (
			id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			tipo_conversion varchar(100) NOT NULL,
			valor_entrada decimal(14,4) NOT NULL DEFAULT 0,
			parametros longtext NULL,
			resultado decimal(14,4) NOT NULL DEFAULT 0,
			created_by bigint(20) unsigned NOT NULL DEFAULT 0,
			created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
			PRIMARY KEY  (id),
			KEY tipo_conversion (tipo_conversion)
		) $charset_collate;";

		$sql[] = 'CREATE TABLE ' . self::table( 'empaques' ) . " (
			id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			nombre varchar(120) NOT NULL,
			unidades_por_caja int(11) NOT NULL DEFAULT 0,
			descripcion text NULL,
			estado varchar(60) NOT NULL DEFAULT 'activo',
			created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
			updated_at datetime NULL,
			PRIMARY KEY  (id),
			KEY estado (estado)
		) $charset_collate;";

		$sql[] = 'CREATE TABLE ' . self::table( 'reportes' ) . " (
			id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			tipo_reporte varchar(100) NOT NULL,
			formato varchar(20) NOT NULL DEFAULT 'csv',
			filtros longtext NULL,
			archivo varchar(190) NOT NULL DEFAULT '',
			created_by bigint(20) unsigned NOT NULL DEFAULT 0,
			created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
			PRIMARY KEY  (id),
			KEY tipo_reporte (tipo_reporte),
			KEY formato (formato)
		) $charset_collate;";

		$sql[] = 'CREATE TABLE ' . self::table( 'inventario_bodega' ) . " (
			id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			codigo varchar(80) NOT NULL,
			descripcion varchar(190) NOT NULL DEFAULT '',
			materia_prima varchar(120) NOT NULL DEFAULT '',
			medida varchar(120) NOT NULL DEFAULT '',
			lote varchar(80) NOT NULL DEFAULT '',
			saldo_bodega decimal(14,3) NOT NULL DEFAULT 0,
			clase varchar(80) NOT NULL DEFAULT '',
			inventario_fisico decimal(14,3) NOT NULL DEFAULT 0,
			ajuste_acumulado decimal(14,3) NOT NULL DEFAULT 0,
			diferencia_ultimo_importe decimal(14,3) NOT NULL DEFAULT 0,
			observaciones text NULL,
			estado varchar(20) NOT NULL DEFAULT 'activo',
			created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
			updated_at datetime NULL,
			PRIMARY KEY  (id),
			UNIQUE KEY codigo_lote (codigo, lote),
			KEY codigo (codigo),
			KEY estado (estado),
			KEY lote (lote),
			KEY created_at (created_at)
		) $charset_collate;";

		$sql[] = 'CREATE TABLE ' . self::table( 'inventario_ajustes' ) . " (
			id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			codigo varchar(80) NOT NULL,
			diferencia decimal(14,3) NOT NULL DEFAULT 0,
			saldo_antes decimal(14,3) NOT NULL DEFAULT 0,
			saldo_despues decimal(14,3) NOT NULL DEFAULT 0,
			inventario_fisico decimal(14,3) NOT NULL DEFAULT 0,
			notas text NULL,
			created_by bigint(20) unsigned NOT NULL DEFAULT 0,
			created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
			PRIMARY KEY  (id),
			KEY codigo (codigo),
			KEY created_at (created_at)
		) $charset_collate;";

		$sql[] = 'CREATE TABLE ' . self::table( 'cierre_caracteristicas' ) . " (
			id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			codigo varchar(80) NOT NULL,
			descripcion varchar(190) NOT NULL DEFAULT '',
			tipo varchar(40) NOT NULL DEFAULT 'cajas',
			largo varchar(40) NOT NULL DEFAULT '',
			ancho varchar(40) NOT NULL DEFAULT '',
			alto varchar(40) NOT NULL DEFAULT '',
			clave varchar(80) NOT NULL DEFAULT '',
			calibre varchar(40) NOT NULL DEFAULT '',
			created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
			updated_at datetime NULL,
			PRIMARY KEY  (id),
			UNIQUE KEY codigo (codigo),
			KEY tipo (tipo)
		) $charset_collate;";

		$sql[] = 'CREATE TABLE ' . self::table( 'planeacion_impresion' ) . " (
			id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			semana_inicio date NOT NULL,
			maquina varchar(40) NOT NULL,
			jornada_inicio smallint(5) unsigned NOT NULL DEFAULT 420,
			jornada_fin smallint(5) unsigned NOT NULL DEFAULT 1020,
			day_hours longtext NULL,
			include_lunch tinyint(1) NOT NULL DEFAULT 1,
			lunch_start smallint(5) unsigned NOT NULL DEFAULT 720,
			lunch_end smallint(5) unsigned NOT NULL DEFAULT 780,
			tasks longtext NOT NULL,
			updated_at datetime NULL,
			updated_by bigint(20) unsigned NOT NULL DEFAULT 0,
			PRIMARY KEY  (id),
			UNIQUE KEY semana_maquina (semana_inicio, maquina),
			KEY maquina (maquina)
		) $charset_collate;";

		$sql[] = 'CREATE TABLE ' . self::table( 'planeacion_formado' ) . " (
			id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			semana_inicio date NOT NULL,
			maquina varchar(40) NOT NULL,
			jornada_inicio smallint(5) unsigned NOT NULL DEFAULT 420,
			jornada_fin smallint(5) unsigned NOT NULL DEFAULT 1020,
			day_hours longtext NULL,
			include_lunch tinyint(1) NOT NULL DEFAULT 1,
			lunch_start smallint(5) unsigned NOT NULL DEFAULT 720,
			lunch_end smallint(5) unsigned NOT NULL DEFAULT 780,
			tasks longtext NOT NULL,
			updated_at datetime NULL,
			updated_by bigint(20) unsigned NOT NULL DEFAULT 0,
			PRIMARY KEY  (id),
			UNIQUE KEY semana_maquina (semana_inicio, maquina),
			KEY maquina (maquina)
		) $charset_collate;";

		$sql[] = 'CREATE TABLE ' . self::table( 'etiqueta_productos' ) . " (
			id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			referencia varchar(190) NOT NULL,
			tipo_producto varchar(40) NOT NULL DEFAULT '',
			mercado varchar(8) NOT NULL DEFAULT 'NAC',
			tamano varchar(80) NOT NULL DEFAULT '',
			codigo_producto char(6) NOT NULL DEFAULT '',
			unidad_empaque varchar(32) NOT NULL DEFAULT '',
			observaciones text NULL,
			created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
			updated_at datetime NULL,
			PRIMARY KEY  (id),
			KEY referencia (referencia(120)),
			KEY tipo_mercado (tipo_producto, mercado)
		) $charset_collate;";

		$sql[] = 'CREATE TABLE ' . self::table( 'etiqueta_pack_historial' ) . " (
			id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			catalog_id bigint(20) unsigned NOT NULL DEFAULT 0,
			paquetes varchar(32) NOT NULL DEFAULT '',
			und_por_paquete varchar(32) NOT NULL DEFAULT '',
			last_used_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
			created_by bigint(20) unsigned NOT NULL DEFAULT 0,
			PRIMARY KEY  (id),
			UNIQUE KEY catalog_combo (catalog_id, paquetes, und_por_paquete),
			KEY catalog_id (catalog_id),
			KEY last_used_at (last_used_at)
		) $charset_collate;";

		$sql[] = 'CREATE TABLE ' . self::table( 'rotulo_rollos' ) . " (
			id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			tipo varchar(80) NOT NULL DEFAULT '',
			tamano varchar(80) NOT NULL DEFAULT '',
			observaciones text NULL,
			created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
			updated_at datetime NULL,
			PRIMARY KEY  (id),
			KEY tipo (tipo),
			KEY tamano (tamano)
		) $charset_collate;";

		$sql[] = 'CREATE TABLE ' . self::table( 'rotulo_rollos_print_historial' ) . " (
			id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			rotulo_id bigint(20) unsigned NOT NULL DEFAULT 0,
			lote_inicio varchar(80) NOT NULL DEFAULT '',
			lote_fin varchar(80) NOT NULL DEFAULT '',
			num_consecutivo int(11) unsigned NOT NULL DEFAULT 1,
			num_copias int(11) unsigned NOT NULL DEFAULT 1,
			gramaje varchar(80) NOT NULL DEFAULT '',
			pesos_json text NULL,
			printed_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
			created_by bigint(20) unsigned NOT NULL DEFAULT 0,
			PRIMARY KEY  (id),
			KEY rotulo_id (rotulo_id),
			KEY printed_at (printed_at)
		) $charset_collate;";

		$sql[] = 'CREATE TABLE ' . self::table( 'pedidos_inventario' ) . " (
			id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			board_slug varchar(80) NOT NULL DEFAULT '',
			item varchar(80) NOT NULL DEFAULT '',
			referencia varchar(190) NOT NULL DEFAULT '',
			cantidad_op decimal(14,3) NOT NULL DEFAULT 0,
			fecha_op date NULL,
			reporte_hornitos decimal(14,3) NOT NULL DEFAULT 0,
			valor_und decimal(14,3) NOT NULL DEFAULT 0,
			stock_latingraf decimal(14,3) NOT NULL DEFAULT 0,
			en_produccion tinyint(1) NOT NULL DEFAULT 0,
			created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
			updated_at datetime NULL,
			PRIMARY KEY  (id),
			KEY board_slug (board_slug),
			KEY referencia (referencia(120)),
			KEY fecha_op (fecha_op)
		) $charset_collate;";

		$sql[] = 'CREATE TABLE ' . self::table( 'pedidos_entregas' ) . " (
			id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			inventario_id bigint(20) unsigned NOT NULL DEFAULT 0,
			slot_index smallint(5) unsigned NOT NULL DEFAULT 0,
			cantidad decimal(14,3) NOT NULL DEFAULT 0,
			remision varchar(120) NOT NULL DEFAULT '',
			source varchar(40) NOT NULL DEFAULT 'manual',
			orden_linea_id bigint(20) unsigned NOT NULL DEFAULT 0,
			orden_slot_index smallint(5) unsigned NOT NULL DEFAULT 0,
			created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
			updated_at datetime NULL,
			PRIMARY KEY  (id),
			KEY inventario_id (inventario_id),
			KEY orden_linea_id (orden_linea_id),
			KEY source (source)
		) $charset_collate;";

		$sql[] = 'CREATE TABLE ' . self::table( 'pedidos_ordenes_compra' ) . " (
			id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			board_slug varchar(80) NOT NULL DEFAULT '',
			fecha_oc date NULL,
			oc_numero varchar(80) NOT NULL DEFAULT '',
			cliente varchar(190) NOT NULL DEFAULT '',
			created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
			updated_at datetime NULL,
			PRIMARY KEY  (id),
			KEY board_slug (board_slug),
			KEY fecha_oc (fecha_oc),
			KEY oc_numero (oc_numero)
		) $charset_collate;";

		$sql[] = 'CREATE TABLE ' . self::table( 'pedidos_ordenes_lineas' ) . " (
			id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			orden_id bigint(20) unsigned NOT NULL DEFAULT 0,
			articulo varchar(190) NOT NULL DEFAULT '',
			descripcion text NULL,
			cantidad decimal(14,3) NOT NULL DEFAULT 0,
			cantidad_maxima_recibida decimal(14,3) NOT NULL DEFAULT 10,
			precio_unit decimal(14,3) NOT NULL DEFAULT 0,
			fecha_entrega date NULL,
			fecha_prorroga date NULL,
			observaciones text NULL,
			op varchar(80) NOT NULL DEFAULT '',
			fecha_aprobacion date NULL,
			estado varchar(40) NOT NULL DEFAULT 'sin_confirmar',
			cantidad_entregada decimal(14,3) NOT NULL DEFAULT 0,
			factura varchar(120) NOT NULL DEFAULT '',
			created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
			updated_at datetime NULL,
			PRIMARY KEY  (id),
			KEY orden_id (orden_id),
			KEY articulo (articulo(120)),
			KEY estado (estado)
		) $charset_collate;";

		$sql[] = 'CREATE TABLE ' . self::table( 'pedidos_clientes' ) . " (
			id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			nombre varchar(190) NOT NULL DEFAULT '',
			codigo varchar(40) NOT NULL DEFAULT '',
			contacto varchar(190) NOT NULL DEFAULT '',
			telefono varchar(60) NOT NULL DEFAULT '',
			email varchar(190) NOT NULL DEFAULT '',
			notas text NULL,
			created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
			updated_at datetime NULL,
			PRIMARY KEY  (id),
			KEY nombre (nombre(120))
		) $charset_collate;";

		$sql[] = 'CREATE TABLE ' . self::table( 'pedidos_orden_linea_entregas' ) . " (
			id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			orden_linea_id bigint(20) unsigned NOT NULL DEFAULT 0,
			slot_index smallint(5) unsigned NOT NULL DEFAULT 0,
			cantidad decimal(14,3) NOT NULL DEFAULT 0,
			factura varchar(120) NOT NULL DEFAULT '',
			created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
			updated_at datetime NULL,
			PRIMARY KEY  (id),
			UNIQUE KEY orden_linea_slot (orden_linea_id, slot_index),
			KEY orden_linea_id (orden_linea_id)
		) $charset_collate;";

		$sql[] = 'CREATE TABLE ' . self::table( 'minutas' ) . " (
			id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			minuta varchar(40) NOT NULL DEFAULT '',
			fecha varchar(30) NOT NULL DEFAULT '',
			orden varchar(40) NOT NULL DEFAULT '',
			cod_cliente varchar(40) NOT NULL DEFAULT '',
			cliente varchar(190) NOT NULL DEFAULT '',
			cod_referencia varchar(40) NOT NULL DEFAULT '',
			referencia varchar(190) NOT NULL DEFAULT '',
			cod_maq varchar(40) NOT NULL DEFAULT '',
			maquina varchar(120) NOT NULL DEFAULT '',
			cod_pro varchar(40) NOT NULL DEFAULT '',
			proceso varchar(120) NOT NULL DEFAULT '',
			hora_inici varchar(30) NOT NULL DEFAULT '',
			hora_final varchar(30) NOT NULL DEFAULT '',
			minutos varchar(20) NOT NULL DEFAULT '',
			buenas varchar(20) NOT NULL DEFAULT '',
			malas varchar(20) NOT NULL DEFAULT '',
			turno varchar(40) NOT NULL DEFAULT '',
			codigo_emp varchar(40) NOT NULL DEFAULT '',
			empleado varchar(120) NOT NULL DEFAULT '',
			paso varchar(80) NOT NULL DEFAULT '',
			equipo varchar(80) NOT NULL DEFAULT '',
			estado varchar(60) NOT NULL DEFAULT '',
			fecha_sys varchar(40) NOT NULL DEFAULT '',
			observacio text NULL,
			mano_obra varchar(40) NOT NULL DEFAULT '',
			maquila varchar(40) NOT NULL DEFAULT '',
			costo_admn varchar(40) NOT NULL DEFAULT '',
			opeaux varchar(40) NOT NULL DEFAULT '',
			revision varchar(30) NOT NULL DEFAULT 'Pendiente',
			created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
			updated_at datetime NULL,
			PRIMARY KEY  (id),
			KEY minuta (minuta),
			KEY fecha (fecha),
			KEY orden (orden),
			KEY cliente (cliente),
			KEY revision (revision)
		) $charset_collate;";

		$sql[] = 'CREATE TABLE ' . self::table( 'auditoria' ) . " (
			id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			module_key varchar(80) NOT NULL DEFAULT '',
			entity_type varchar(80) NOT NULL DEFAULT '',
			entity_id varchar(80) NOT NULL DEFAULT '',
			action varchar(40) NOT NULL DEFAULT '',
			summary varchar(255) NOT NULL DEFAULT '',
			payload_before longtext NULL,
			payload_after longtext NULL,
			meta longtext NULL,
			created_by bigint(20) unsigned NOT NULL DEFAULT 0,
			created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
			PRIMARY KEY  (id),
			KEY module_entity (module_key, entity_type, entity_id),
			KEY action (action),
			KEY created_at (created_at)
		) $charset_collate;";

		$sql[] = 'CREATE TABLE ' . self::table( 'papelera' ) . " (
			id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			module_key varchar(80) NOT NULL DEFAULT '',
			entity_type varchar(80) NOT NULL DEFAULT '',
			entity_id varchar(80) NOT NULL DEFAULT '',
			entity_label varchar(255) NOT NULL DEFAULT '',
			payload longtext NULL,
			status varchar(20) NOT NULL DEFAULT 'deleted',
			deleted_by bigint(20) unsigned NOT NULL DEFAULT 0,
			deleted_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
			restored_by bigint(20) unsigned NOT NULL DEFAULT 0,
			restored_at datetime NULL,
			PRIMARY KEY  (id),
			KEY module_entity (module_key, entity_type, entity_id),
			KEY status (status),
			KEY deleted_at (deleted_at)
		) $charset_collate;";

		$sql[] = 'CREATE TABLE ' . self::table( 'versiones' ) . " (
			id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			module_key varchar(80) NOT NULL DEFAULT '',
			entity_type varchar(80) NOT NULL DEFAULT '',
			entity_id varchar(80) NOT NULL DEFAULT '',
			version_no int(11) unsigned NOT NULL DEFAULT 1,
			summary varchar(255) NOT NULL DEFAULT '',
			payload longtext NULL,
			created_by bigint(20) unsigned NOT NULL DEFAULT 0,
			created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
			PRIMARY KEY  (id),
			KEY module_entity_version (module_key, entity_type, entity_id, version_no),
			KEY created_at (created_at)
		) $charset_collate;";

		foreach ( $sql as $statement ) {
			dbDelta( $statement );
		}

		self::migrate_inventario_bodega_codigo_lote();
		self::migrate_planeacion_day_hours();
		self::migrate_planeacion_include_lunch();
		self::migrate_planeacion_lunch_hours();
		self::ensure_planeacion_impresion_table();
		self::ensure_planeacion_formado_table();
		self::migrate_pedidos_orden_linea_entregas();
		self::migrate_pedidos_entregas_inventory_deduction();
	}

	/**
	 * Agrega day_hours y rellena desde jornada global legacy.
	 *
	 * @return void
	 */
	public static function migrate_planeacion_day_hours() {
		self::migrate_planeacion_day_hours_for_table( 'planeacion_impresion' );
		self::migrate_planeacion_day_hours_for_table( 'planeacion_formado' );
	}

	/**
	 * Agrega include_lunch a tablas de planeación.
	 *
	 * @return void
	 */
	public static function migrate_planeacion_include_lunch() {
		self::migrate_planeacion_include_lunch_for_table( 'planeacion_impresion' );
		self::migrate_planeacion_include_lunch_for_table( 'planeacion_formado' );
	}

	/**
	 * @param string $table_key Clave de tabla (planeacion_impresion|planeacion_formado).
	 * @return void
	 */
	private static function migrate_planeacion_include_lunch_for_table( $table_key ) {
		global $wpdb;

		$table = self::table( $table_key );
		if ( ! $table ) {
			return;
		}

		$column = $wpdb->get_results( "SHOW COLUMNS FROM {$table} LIKE 'include_lunch'", ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		if ( empty( $column ) ) {
			$wpdb->query( "ALTER TABLE {$table} ADD include_lunch tinyint(1) NOT NULL DEFAULT 1 AFTER day_hours" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		}
	}

	/**
	 * Agrega lunch_start y lunch_end a tablas de planeación.
	 *
	 * @return void
	 */
	public static function migrate_planeacion_lunch_hours() {
		self::migrate_planeacion_lunch_hours_for_table( 'planeacion_impresion' );
		self::migrate_planeacion_lunch_hours_for_table( 'planeacion_formado' );
	}

	/**
	 * @param string $table_key Clave de tabla (planeacion_impresion|planeacion_formado).
	 * @return void
	 */
	private static function migrate_planeacion_lunch_hours_for_table( $table_key ) {
		global $wpdb;

		$table = self::table( $table_key );
		if ( ! $table ) {
			return;
		}

		$column = $wpdb->get_results( "SHOW COLUMNS FROM {$table} LIKE 'lunch_start'", ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		if ( empty( $column ) ) {
			$wpdb->query( "ALTER TABLE {$table} ADD lunch_start smallint(5) unsigned NOT NULL DEFAULT 720 AFTER include_lunch" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
			$wpdb->query( "ALTER TABLE {$table} ADD lunch_end smallint(5) unsigned NOT NULL DEFAULT 780 AFTER lunch_start" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		}
	}

	/**
	 * Agrega day_hours y rellena desde jornada global legacy en una tabla de planeación.
	 *
	 * @param string $table_key Clave de tabla (planeacion_impresion|planeacion_formado).
	 * @return void
	 */
	private static function migrate_planeacion_day_hours_for_table( $table_key ) {
		global $wpdb;

		$table = self::table( $table_key );
		if ( ! $table ) {
			return;
		}

		$column = $wpdb->get_results( "SHOW COLUMNS FROM {$table} LIKE 'day_hours'", ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		if ( empty( $column ) ) {
			$wpdb->query( "ALTER TABLE {$table} ADD day_hours longtext NULL AFTER jornada_fin" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		}

		$rows = $wpdb->get_results( "SELECT id, jornada_inicio, jornada_fin, day_hours FROM {$table}", ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		if ( ! is_array( $rows ) ) {
			return;
		}

		foreach ( $rows as $row ) {
			$existing = json_decode( (string) ( $row['day_hours'] ?? '' ), true );
			if ( is_array( $existing ) && ! empty( $existing ) ) {
				continue;
			}

			$start = max( 0, (int) ( $row['jornada_inicio'] ?? 420 ) );
			$end   = max( $start + 60, (int) ( $row['jornada_fin'] ?? 1020 ) );
			$hours = array();

			for ( $day = 0; $day <= 6; $day++ ) {
				$hours[ (string) $day ] = array(
					'start' => $start,
					'end'   => $end,
				);
			}

			$wpdb->update(
				$table,
				array( 'day_hours' => wp_json_encode( $hours ) ),
				array( 'id' => (int) ( $row['id'] ?? 0 ) ),
				array( '%s' ),
				array( '%d' )
			);
		}
	}

	/**
	 * Garantiza que la tabla de planeación de impresión exista.
	 *
	 * @return void
	 */
	public static function ensure_planeacion_impresion_table() {
		global $wpdb;

		$table = self::table( 'planeacion_impresion' );
		if ( ! $table ) {
			return;
		}

		$exists = $wpdb->get_var(
			$wpdb->prepare(
				'SHOW TABLES LIKE %s',
				$table
			)
		);

		if ( $exists !== $table ) {
			self::create_tables();
		}

		self::migrate_planeacion_day_hours();
		self::migrate_planeacion_include_lunch();
		self::migrate_planeacion_lunch_hours();
	}

	/**
	 * Garantiza que la tabla de planeación de formado exista.
	 *
	 * @return void
	 */
	public static function ensure_planeacion_formado_table() {
		global $wpdb;

		$table = self::table( 'planeacion_formado' );
		if ( ! $table ) {
			return;
		}

		$exists = $wpdb->get_var(
			$wpdb->prepare(
				'SHOW TABLES LIKE %s',
				$table
			)
		);

		if ( $exists !== $table ) {
			self::create_tables();
		}

		self::migrate_planeacion_day_hours();
		self::migrate_planeacion_include_lunch();
		self::migrate_planeacion_lunch_hours();
	}

	/**
	 * Garantiza que la tabla de caracteristicas exista (p. ej. si la version ya estaba actualizada).
	 *
	 * @return void
	 */
	public static function ensure_cierre_caracteristicas_table() {
		global $wpdb;

		$table = self::table( 'cierre_caracteristicas' );
		if ( ! $table ) {
			return;
		}

		$exists = $wpdb->get_var(
			$wpdb->prepare(
				'SHOW TABLES LIKE %s',
				$table
			)
		);

		if ( $exists !== $table ) {
			self::create_tables();
		}

		$calibre_column = $wpdb->get_results( "SHOW COLUMNS FROM {$table} LIKE 'calibre'", ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		if ( empty( $calibre_column ) ) {
			$wpdb->query( "ALTER TABLE {$table} ADD calibre varchar(40) NOT NULL DEFAULT '' AFTER clave" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		}
	}

	/**
	 * Garantiza la tabla de códigos de producto para etiquetas.
	 *
	 * @return void
	 */
	public static function ensure_etiqueta_productos_table() {
		global $wpdb;

		$table = self::table( 'etiqueta_productos' );
		if ( ! $table ) {
			return;
		}

		$exists = $wpdb->get_var(
			$wpdb->prepare(
				'SHOW TABLES LIKE %s',
				$table
			)
		);

		if ( $exists !== $table ) {
			self::create_tables();
		}

		$column = $wpdb->get_results( "SHOW COLUMNS FROM {$table} LIKE 'unidad_empaque'", ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		if ( empty( $column ) ) {
			$wpdb->query( "ALTER TABLE {$table} ADD unidad_empaque varchar(32) NOT NULL DEFAULT '' AFTER codigo_producto" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		}
	}

	/**
	 * Crea la tabla de historial de paquetes/unidades por etiqueta si no existe.
	 *
	 * @return void
	 */
	public static function ensure_etiqueta_pack_historial_table() {
		global $wpdb;

		$table = self::table( 'etiqueta_pack_historial' );
		if ( ! $table ) {
			return;
		}

		$exists = $wpdb->get_var(
			$wpdb->prepare(
				'SHOW TABLES LIKE %s',
				$table
			)
		);

		if ( $exists !== $table ) {
			self::create_tables();
		}
	}

	/**
	 * Crea la tabla de rótulos de rollos si no existe.
	 *
	 * @return void
	 */
	public static function ensure_rotulo_rollos_table() {
		global $wpdb;

		$table = self::table( 'rotulo_rollos' );
		if ( ! $table ) {
			return;
		}

		$exists = $wpdb->get_var(
			$wpdb->prepare(
				'SHOW TABLES LIKE %s',
				$table
			)
		);

		if ( $exists !== $table ) {
			self::create_tables();
		}
	}

	/**
	 * Crea la tabla de historial de impresión de rótulos si no existe.
	 *
	 * @return void
	 */
	public static function ensure_rotulo_rollos_print_historial_table() {
		global $wpdb;

		$table = self::table( 'rotulo_rollos_print_historial' );
		if ( ! $table ) {
			return;
		}

		$exists = $wpdb->get_var(
			$wpdb->prepare(
				'SHOW TABLES LIKE %s',
				$table
			)
		);

		if ( $exists !== $table ) {
			self::create_tables();
		}
	}

	/**
	 * Crea la tabla de minutas si no existe.
	 *
	 * @return void
	 */
	public static function ensure_minutas_table() {
		global $wpdb;

		$table = self::table( 'minutas' );
		if ( ! $table ) {
			return;
		}

		$exists = $wpdb->get_var(
			$wpdb->prepare(
				'SHOW TABLES LIKE %s',
				$table
			)
		);

		if ( $exists !== $table ) {
			self::create_tables();
		}

		self::migrate_minutas_drop_unique_minuta();
	}

	/**
	 * Permite varias filas con el mismo número de minuta (procesos/pasos distintos).
	 *
	 * @return void
	 */
	public static function migrate_minutas_drop_unique_minuta() {
		global $wpdb;

		$table = self::table( 'minutas' );
		if ( ! $table ) {
			return;
		}

		$indexes = $wpdb->get_results( "SHOW INDEX FROM {$table}", ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		if ( ! is_array( $indexes ) ) {
			return;
		}

		$has_unique_minuta  = false;
		$has_regular_minuta = false;

		foreach ( $indexes as $index ) {
			if ( 'minuta' !== ( $index['Key_name'] ?? '' ) ) {
				continue;
			}

			if ( '0' === (string) ( $index['Non_unique'] ?? '1' ) ) {
				$has_unique_minuta = true;
			} else {
				$has_regular_minuta = true;
			}
		}

		if ( $has_unique_minuta ) {
			$wpdb->query( "ALTER TABLE {$table} DROP INDEX minuta" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
			$has_regular_minuta = false;
		}

		if ( ! $has_regular_minuta ) {
			$wpdb->query( "ALTER TABLE {$table} ADD INDEX minuta (minuta)" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		}
	}

	/**
	 * Crea tablas del cuadro de pedidos si no existen.
	 *
	 * @return void
	 */
	public static function ensure_pedidos_board_tables() {
		global $wpdb;

		$tables = array(
			'pedidos_inventario',
			'pedidos_entregas',
			'pedidos_ordenes_compra',
			'pedidos_ordenes_lineas',
			'pedidos_orden_linea_entregas',
			'pedidos_clientes',
		);

		foreach ( $tables as $key ) {
			$table = self::table( $key );
			if ( ! $table ) {
				continue;
			}

			$exists = $wpdb->get_var(
				$wpdb->prepare(
					'SHOW TABLES LIKE %s',
					$table
				)
			);

			if ( $exists !== $table ) {
				self::create_tables();
				return;
			}
		}

		self::migrate_pedidos_orden_linea_entregas();
		self::migrate_pedidos_entregas_inventory_deduction();
		self::migrate_pedidos_ordenes_extra_columns();
		Logistica_Pedidos_Board::migrate_pedidos_clientes_from_ordenes();
	}

	/**
	 * Añade columnas extra en órdenes de compra (cliente, descripción, cant. máx.).
	 *
	 * @return void
	 */
	public static function migrate_pedidos_ordenes_extra_columns() {
		global $wpdb;

		$ordenes_table = self::table( 'pedidos_ordenes_compra' );
		$lineas_table  = self::table( 'pedidos_ordenes_lineas' );

		if ( $ordenes_table ) {
			$column = $wpdb->get_results( "SHOW COLUMNS FROM {$ordenes_table} LIKE 'cliente'", ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
			if ( empty( $column ) ) {
				$wpdb->query( "ALTER TABLE {$ordenes_table} ADD cliente varchar(190) NOT NULL DEFAULT '' AFTER oc_numero" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
			}
		}

		if ( ! $lineas_table ) {
			return;
		}

		$descripcion = $wpdb->get_results( "SHOW COLUMNS FROM {$lineas_table} LIKE 'descripcion'", ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		if ( empty( $descripcion ) ) {
			$wpdb->query( "ALTER TABLE {$lineas_table} ADD descripcion text NULL AFTER articulo" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		}

		$cant_max = $wpdb->get_results( "SHOW COLUMNS FROM {$lineas_table} LIKE 'cantidad_maxima_recibida'", ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		if ( empty( $cant_max ) ) {
			$wpdb->query( "ALTER TABLE {$lineas_table} ADD cantidad_maxima_recibida decimal(14,3) NOT NULL DEFAULT 10 AFTER cantidad" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		}

		$fecha_prorroga = $wpdb->get_results( "SHOW COLUMNS FROM {$lineas_table} LIKE 'fecha_prorroga'", ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		if ( empty( $fecha_prorroga ) ) {
			$wpdb->query( "ALTER TABLE {$lineas_table} ADD fecha_prorroga date NULL AFTER fecha_entrega" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		}

		$op_col = $wpdb->get_results( "SHOW COLUMNS FROM {$lineas_table} LIKE 'op'", ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		if ( empty( $op_col ) ) {
			$wpdb->query( "ALTER TABLE {$lineas_table} ADD op varchar(80) NOT NULL DEFAULT '' AFTER observaciones" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		}

		$fecha_aprobacion_col = $wpdb->get_results( "SHOW COLUMNS FROM {$lineas_table} LIKE 'fecha_aprobacion'", ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		if ( empty( $fecha_aprobacion_col ) ) {
			$wpdb->query( "ALTER TABLE {$lineas_table} ADD fecha_aprobacion date NULL AFTER op" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		}
	}

	/**
	 * Crea tabla de entregas parciales OC y migra datos legacy.
	 *
	 * @return void
	 */
	public static function migrate_pedidos_orden_linea_entregas() {
		global $wpdb;

		$entregas_table = self::table( 'pedidos_entregas' );
		if ( $entregas_table ) {
			$column = $wpdb->get_results( "SHOW COLUMNS FROM {$entregas_table} LIKE 'orden_slot_index'", ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
			if ( empty( $column ) ) {
				$wpdb->query( "ALTER TABLE {$entregas_table} ADD orden_slot_index smallint(5) unsigned NOT NULL DEFAULT 0 AFTER orden_linea_id" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
			}
		}

		$parciales_table = self::table( 'pedidos_orden_linea_entregas' );
		$lineas_table    = self::table( 'pedidos_ordenes_lineas' );
		if ( ! $parciales_table || ! $lineas_table ) {
			return;
		}

		$exists = $wpdb->get_var(
			$wpdb->prepare(
				'SHOW TABLES LIKE %s',
				$parciales_table
			)
		);
		if ( $exists !== $parciales_table ) {
			self::create_tables();
			return;
		}

		$lineas = $wpdb->get_results(
			"SELECT id, cantidad_entregada, factura FROM {$lineas_table} WHERE cantidad_entregada > 0",
			ARRAY_A
		);
		if ( ! is_array( $lineas ) ) {
			return;
		}

		foreach ( $lineas as $linea ) {
			$linea_id = (int) ( $linea['id'] ?? 0 );
			if ( $linea_id <= 0 ) {
				continue;
			}

			$has_parcial = (int) $wpdb->get_var(
				$wpdb->prepare(
					"SELECT COUNT(*) FROM {$parciales_table} WHERE orden_linea_id = %d",
					$linea_id
				)
			);
			if ( $has_parcial > 0 ) {
				continue;
			}

			$wpdb->insert(
				$parciales_table,
				array(
					'orden_linea_id' => $linea_id,
					'slot_index'     => 0,
					'cantidad'       => (float) ( $linea['cantidad_entregada'] ?? 0 ),
					'factura'        => sanitize_text_field( (string) ( $linea['factura'] ?? '' ) ),
					'created_at'     => current_time( 'mysql' ),
					'updated_at'     => current_time( 'mysql' ),
				),
				array( '%d', '%d', '%f', '%s', '%s', '%s' )
			);
		}
	}

	/**
	 * Descuenta entregas históricas del reporte Hornitos y stock Latingraf almacenados.
	 *
	 * @return void
	 */
	public static function migrate_pedidos_entregas_inventory_deduction() {
		if ( get_option( 'logistica_pedidos_entregas_deducted_v1' ) ) {
			return;
		}

		global $wpdb;

		$inv_table = self::table( 'pedidos_inventario' );
		$ent_table = self::table( 'pedidos_entregas' );
		if ( ! $inv_table || ! $ent_table ) {
			return;
		}

		$rows = $wpdb->get_results( "SELECT id FROM {$inv_table}", ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		if ( ! is_array( $rows ) ) {
			update_option( 'logistica_pedidos_entregas_deducted_v1', '1', false );
			return;
		}

		foreach ( $rows as $row ) {
			$inventario_id = (int) ( $row['id'] ?? 0 );
			if ( $inventario_id <= 0 ) {
				continue;
			}

			$entregas_total = (float) $wpdb->get_var(
				$wpdb->prepare(
					"SELECT COALESCE(SUM(cantidad), 0) FROM {$ent_table} WHERE inventario_id = %d",
					$inventario_id
				)
			);

			if ( $entregas_total <= 0 ) {
				continue;
			}

			$inv_row = $wpdb->get_row(
				$wpdb->prepare( "SELECT reporte_hornitos, stock_latingraf FROM {$inv_table} WHERE id = %d", $inventario_id ),
				ARRAY_A
			);
			if ( ! is_array( $inv_row ) ) {
				continue;
			}

			$wpdb->update(
				$inv_table,
				array(
					'reporte_hornitos' => max( 0, (float) ( $inv_row['reporte_hornitos'] ?? 0 ) - $entregas_total ),
					'stock_latingraf'  => max( 0, (float) ( $inv_row['stock_latingraf'] ?? 0 ) - $entregas_total ),
					'updated_at'       => current_time( 'mysql' ),
				),
				array( 'id' => $inventario_id ),
				array( '%f', '%f', '%s' ),
				array( '%d' )
			);
		}

		update_option( 'logistica_pedidos_entregas_deducted_v1', '1', false );
	}

	/**
	 * Permite varios lotes por codigo (clave compuesta codigo + lote).
	 *
	 * @return void
	 */
	public static function migrate_inventario_bodega_codigo_lote() {
		global $wpdb;

		$table = self::table( 'inventario_bodega' );
		if ( ! $table ) {
			return;
		}

		$indexes              = $wpdb->get_results( "SHOW INDEX FROM {$table}", ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		$has_codigo_unique    = false;
		$has_codigo_lote      = false;
		$has_codigo_nonunique = false;

		if ( is_array( $indexes ) ) {
			foreach ( $indexes as $index ) {
				$key_name   = (string) ( $index['Key_name'] ?? '' );
				$non_unique = (string) ( $index['Non_unique'] ?? '1' );

				if ( 'codigo' === $key_name && '0' === $non_unique ) {
					$has_codigo_unique = true;
				}

				if ( 'codigo' === $key_name && '1' === $non_unique ) {
					$has_codigo_nonunique = true;
				}

				if ( 'codigo_lote' === $key_name && '0' === $non_unique ) {
					$has_codigo_lote = true;
				}
			}
		}

		if ( $has_codigo_lote ) {
			update_option( 'logistica_inventario_codigo_lote_key', '1' );
			return;
		}

		if ( $has_codigo_unique ) {
			$wpdb->query( "ALTER TABLE {$table} DROP INDEX codigo" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		}

		$wpdb->query( "ALTER TABLE {$table} ADD UNIQUE KEY codigo_lote (codigo, lote)" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared

		if ( ! $has_codigo_nonunique ) {
			$wpdb->query( "ALTER TABLE {$table} ADD KEY codigo (codigo)" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		}

		update_option( 'logistica_inventario_codigo_lote_key', '1' );
	}

	/**
	 * Crea tablas de trazabilidad (auditoría, papelera, versiones) si no existen.
	 *
	 * @return void
	 */
	public static function ensure_lifecycle_tables() {
		global $wpdb;

		$keys = array( 'auditoria', 'papelera', 'versiones' );
		foreach ( $keys as $key ) {
			$table = self::table( $key );
			if ( ! $table ) {
				continue;
			}

			$exists = $wpdb->get_var(
				$wpdb->prepare(
					'SHOW TABLES LIKE %s',
					$table
				)
			);

			if ( $exists !== $table ) {
				self::create_tables();
				return;
			}
		}
	}
}
