-- ============================================================================= -- 06_master_sync.sql -- Migración maestra de sincronización local ↔ producción -- Generada: 2025-01-01 -- -- INSTRUCCIONES DE APLICACIÓN: -- LOCAL (Docker MySQL): -- docker exec whatsapp-dev-mysql mysql -uroot -proot_password_2026 \ -- usite_whatsapp_bot < database/06_master_sync.sql -- -- PRODUCCIÓN (desde el contenedor app): -- - Aplica SOLO las secciones marcadas [SOLO PROD] si prod ya las tiene, -- o la migración completa en instalaciones nuevas. -- -- GARANTÍA DE SEGURIDAD: -- Este script es COMPLETAMENTE NO DESTRUCTIVO. No contiene DROP TABLE, -- TRUNCATE, DELETE ni UPDATE. Todas las operaciones usan IF NOT EXISTS o -- INSERT IGNORE, por lo que correrlo en un servidor con datos existentes -- solo creará lo que falte sin modificar ni eliminar nada. -- -- RESUMEN DE CAMBIOS: -- A) Tablas solo en LOCAL → agregar a producción y futuras instalaciones: -- - scheduled_messages -- - scheduled_messages_view (VIEW) -- B) Tablas solo en PROD → agregar a local y futuras instalaciones: -- - roles -- - role_modules -- - lab_config -- C) Columnas en PROD que faltan en LOCAL y futuras instalaciones: -- - admin_users.role_id -- - lab_form_envios.hash_verificacion -- - lab_formularios.doc_encabezado, doc_subtitulo, doc_logo_base64, -- doc_color, doc_pie_pagina -- D) Ya cubierto en 05_terms_acceptance.sql (no se repite): -- - terms_versions, terms_acceptance -- - users.terms_pending, terms_accepted_at, terms_version_id -- ============================================================================= SET NAMES utf8mb4; SET foreign_key_checks = 0; -- ============================================================================= -- C0) COLUMNAS: message_templates (guard por si 02 no aplicó completo) -- ============================================================================= ALTER TABLE `message_templates` ADD COLUMN IF NOT EXISTS `body_text` TEXT DEFAULT NULL COMMENT 'Texto del cuerpo con variables {{1}},{{2}}…', ADD COLUMN IF NOT EXISTS `components` LONGTEXT DEFAULT NULL COMMENT 'Componentes JSON completos de la plantilla', ADD COLUMN IF NOT EXISTS `example_parameters` LONGTEXT DEFAULT NULL COMMENT 'Ejemplos de valores para los parámetros', ADD COLUMN IF NOT EXISTS `header_text` VARCHAR(255) DEFAULT NULL COMMENT 'Texto del header', ADD COLUMN IF NOT EXISTS `header_type` ENUM('text','image','video','document') DEFAULT NULL COMMENT 'Tipo de header', ADD COLUMN IF NOT EXISTS `footer_text` VARCHAR(255) DEFAULT NULL COMMENT 'Texto del footer', ADD COLUMN IF NOT EXISTS `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP; ALTER TABLE `message_templates` ADD INDEX IF NOT EXISTS `idx_template_language` (`template_name`, `language_code`); -- ============================================================================= -- A1) TABLA: scheduled_messages (local → prod / instalaciones nuevas) -- ============================================================================= CREATE TABLE IF NOT EXISTS `scheduled_messages` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL, `template_id` int(11) DEFAULT NULL, `template_name` varchar(100) DEFAULT NULL, `template_language` varchar(10) DEFAULT 'es', `template_parameters` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT 'Parámetros para las variables de la plantilla' CHECK (json_valid(`template_parameters`)), `message_type` enum('text','template') DEFAULT 'template', `message_content` text DEFAULT NULL COMMENT 'Contenido del mensaje si es tipo text', `scheduled_date` date NOT NULL, `scheduled_time` time NOT NULL, `status` enum('pending','sent','failed','cancelled') DEFAULT 'pending', `sent_at` datetime DEFAULT NULL, `error_message` text DEFAULT NULL, `created_by` int(11) DEFAULT NULL COMMENT 'ID del admin que creó el recordatorio', `created_at` datetime DEFAULT current_timestamp(), `updated_at` datetime DEFAULT current_timestamp() ON UPDATE current_timestamp(), PRIMARY KEY (`id`), KEY `idx_user_id` (`user_id`), KEY `idx_scheduled_date` (`scheduled_date`), KEY `idx_status` (`status`), KEY `idx_scheduled_datetime` (`scheduled_date`,`scheduled_time`,`status`), CONSTRAINT `scheduled_messages_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- ============================================================================= -- A2) VISTA: scheduled_messages_view (local → prod / instalaciones nuevas) -- ============================================================================= CREATE OR REPLACE VIEW `scheduled_messages_view` AS SELECT `sm`.`id`, `sm`.`user_id`, `sm`.`template_id`, `sm`.`template_name`, `sm`.`template_language`, `sm`.`template_parameters`, `sm`.`message_type`, `sm`.`message_content`, `sm`.`scheduled_date`, `sm`.`scheduled_time`, `sm`.`status`, `sm`.`sent_at`, `sm`.`error_message`, `sm`.`created_by`, `sm`.`created_at`, `sm`.`updated_at`, `u`.`name` AS `user_name`, `u`.`phone_number` AS `phone_number`, `mt`.`name` AS `template_display_name`, `mt`.`body_text` AS `template_body`, `mt`.`status` AS `template_status` FROM `scheduled_messages` `sm` LEFT JOIN `users` `u` ON `sm`.`user_id` = `u`.`id` LEFT JOIN `message_templates` `mt` ON `sm`.`template_id` = `mt`.`id` ORDER BY `sm`.`scheduled_date` DESC, `sm`.`scheduled_time` DESC; -- ============================================================================= -- B1) TABLA: roles (prod → local / instalaciones nuevas) -- ============================================================================= CREATE TABLE IF NOT EXISTS `roles` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(100) NOT NULL COMMENT 'Nombre legible del rol', `slug` varchar(50) NOT NULL COMMENT 'Clave interna (enfermero, admin, …)', `description` text DEFAULT NULL, `color` varchar(20) DEFAULT '#6c757d' COMMENT 'Color HEX para la UI', `is_system` tinyint(1) NOT NULL DEFAULT 0 COMMENT '1 = no se puede eliminar', `created_at` timestamp NOT NULL DEFAULT current_timestamp(), `updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), PRIMARY KEY (`id`), UNIQUE KEY `uk_slug` (`slug`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- Datos semilla mínimos de roles del sistema INSERT IGNORE INTO `roles` (`id`, `name`, `slug`, `description`, `is_system`) VALUES (1, 'Administrador', 'admin', 'Acceso completo al sistema', 1), (2, 'Enfermero', 'enfermero', 'Acceso a módulos de laboratorio y atención', 1); -- ============================================================================= -- B2) TABLA: role_modules (prod → local / instalaciones nuevas) -- ============================================================================= CREATE TABLE IF NOT EXISTS `role_modules` ( `id` int(11) NOT NULL AUTO_INCREMENT, `role_id` int(11) NOT NULL, `module_slug` varchar(100) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_role_module` (`role_id`, `module_slug`), CONSTRAINT `fk_rm_role` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- ============================================================================= -- B3) TABLA: lab_config (prod → local / instalaciones nuevas) -- ============================================================================= CREATE TABLE IF NOT EXISTS `lab_config` ( `clave` varchar(80) NOT NULL, `valor` longtext NOT NULL DEFAULT '', `updated_at` timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), PRIMARY KEY (`clave`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; -- ============================================================================= -- C1) COLUMNA: admin_users.role_id (prod → local / instalaciones nuevas) -- ============================================================================= ALTER TABLE `admin_users` ADD COLUMN IF NOT EXISTS `role_id` int(11) DEFAULT NULL AFTER `password`; -- C1a) FK opcional: se añade solo si no existe ya -- (MariaDB/MySQL 10.x: ADD CONSTRAINT IF NOT EXISTS no es estándar, -- se ignora el error si ya existe usando INSERT IGNORE pattern) ALTER TABLE `admin_users` ADD CONSTRAINT IF NOT EXISTS `fk_admin_role` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE SET NULL ON UPDATE CASCADE; -- ============================================================================= -- C2) COLUMNA: lab_form_envios.hash_verificacion (prod → local) -- ============================================================================= ALTER TABLE `lab_form_envios` ADD COLUMN IF NOT EXISTS `hash_verificacion` char(64) DEFAULT NULL COMMENT 'SHA-256 del PDF generado, para verificación de integridad' AFTER `pdf_path`; -- ============================================================================= -- C3) COLUMNAS: lab_formularios doc_* (prod → local / instalaciones nuevas) -- ============================================================================= ALTER TABLE `lab_formularios` ADD COLUMN IF NOT EXISTS `doc_encabezado` varchar(200) DEFAULT NULL COMMENT 'Título principal del documento PDF', ADD COLUMN IF NOT EXISTS `doc_subtitulo` varchar(200) DEFAULT NULL COMMENT 'Subtítulo / razón social', ADD COLUMN IF NOT EXISTS `doc_logo_base64` longtext DEFAULT NULL COMMENT 'Logo en Base64 para el PDF', ADD COLUMN IF NOT EXISTS `doc_color` varchar(20) DEFAULT NULL COMMENT 'Color principal HEX (#RRGGBB)', ADD COLUMN IF NOT EXISTS `doc_pie_pagina` varchar(500) DEFAULT NULL COMMENT 'Texto del pie de página'; -- ============================================================================= -- FIN -- ============================================================================= SET foreign_key_checks = 1; -- Verificación rápida (opcional, descomentar para depuración) -- SELECT 'scheduled_messages' AS tabla, COUNT(*) AS filas FROM scheduled_messages -- UNION ALL SELECT 'roles', COUNT(*) FROM roles -- UNION ALL SELECT 'role_modules', COUNT(*) FROM role_modules -- UNION ALL SELECT 'lab_config', COUNT(*) FROM lab_config;