-- =============================================================================
--  CAPA 1 (BD) DE LA FRONTERA DE MAESTRÍA  —  REVISIÓN DEL SCRIPT DE TRIGGERS
--  Proyecto : CL032BACK (portal público)   Esquema: asturias_api
--  Archivo  : database/manual/2026_08_18_triggers_rsv_replicacion_maestria.sql
--  Fecha    : 2026-08-18
--  Sustituye a: database/manual/2026_07_24_triggers_rsv_replicacion.sql
-- =============================================================================
--
--  BITÁCORA / PROPÓSITO
--  --------------------
--  Este script es la REVISIÓN del script de triggers del 24-jul, no uno paralelo.
--  Cambia UNA sola cosa y conserva todo lo demás:
--
--    * `sp_rsv_marcar_replicacion` gana la GUARDA DE MAESTRÍA.
--    * LOS 12 TRIGGERS NO CAMBIAN NI UNA LÍNEA. Se recrean idénticos para que este
--      archivo sea autosuficiente y el script del 24-jul quede obsoleto por completo.
--
--  POR QUÉ LA GUARDA VIVE EN EL PROCEDIMIENTO Y NO EN LOS TRIGGERS
--  ---------------------------------------------------------------
--  Los triggers nieto (paquete/extra) tendrían que saltar dos tablas
--  (extra → habitación → reservación) SOLO para leer un valor. Multiplicado por 12
--  triggers, son 12 lugares donde la regla puede divergir cuando alguien toque uno.
--  El procedimiento ya lo comparten los doce: una condición, un solo sitio.
--
--  POR QUÉ NO SE CONDICIONA POR `reservacionEstado`
--  ------------------------------------------------
--  Porque la regla NO es expresable por estado. Un 'Cancelado' que viene de 'Temporal'
--  SÍ debe replicarse (libera cupo en el maestro; sin eso el cupo queda comprometido
--  para siempre); uno que viene de 'Reservado' no puede ni originarse en el portal.
--  El mismo estado destino, dos respuestas opuestas. Además el estado puede RETROCEDER
--  por una corrección manual, y eso le devolvería la maestría al portal en silencio.
--  Ver PROPUESTA_REPLICACION_BIDIRECCIONAL_RESERVACIONES.md §4.
--
--  QUÉ RESUELVE
--  ------------
--  Cuando Reservaciones V2 acepta la venta, el portal sella `reservacionMaestro = 'Privado'` en el
--  mismo commit del paso a 'Reservado'. A partir de ese instante, replicar del portal al
--  privado NO es "un dato de más": es DESTRUCCIÓN DE DATOS. Reservaciones V2 aplica el snapshot con
--  `ReservacionHabitacion::reemplazarDesdeSnapshot()`, que BORRA extras, paquetes y
--  habitaciones locales antes de reinsertar. Una marca tardía del portal borra el extra
--  que el gerente acaba de capturar, sin dejar rastro.
--
--  EFECTO SECUNDARIO DESEADO SOBRE `trg_rsv_reservacion_ad` (AFTER DELETE de cabecera):
--  la fila ya no existe cuando corre el SELECT, así que devuelve NULL y no se marca. Es
--  MÁS SEGURO que antes y no se pierde nada: `ReplicacionController::recolectar()` ya
--  cerraba sin replicar las marcas cuya reservación no existe. Es decir, ese AFTER DELETE
--  ya no hacía nada útil.
--
--  BUCLE DE RETROALIMENTACIÓN DEL CANAL INVERSO: resuelto de regalo. Cuando (Fase 4) el
--  portal aplique una réplica del privado, escribirá en `rsv_reservacion` y disparará sus
--  propios triggers — pero la fila ya es 'Privado', así que no se marca nada. NO hace
--  falta ninguna bandera de sesión tipo `SET @replicando = 1`.
--
--  ⚠⚠ REQUISITO PREVIO — LEER ANTES DE EJECUTAR ⚠⚠
--  ------------------------------------------------
--  LA COLUMNA `rsv_reservacion.reservacionMaestro` DEBE EXISTIR YA.
--  Aplica primero UNA de estas dos:
--      php artisan migrate      (2026_08_18_120000_add_maestria_to_rsv_reservacion.php)
--      database/sql/rsv_reservacion_add_maestria.sql
--
--  SI SE INSTALA ESTE PROCEDIMIENTO SIN LA COLUMNA, EL `SELECT ... INTO` FALLARÁ EN
--  TIEMPO DE EJECUCIÓN Y, COMO CORRE DENTRO DE UN TRIGGER, ABORTARÁ LAS ESCRITURAS DE
--  NEGOCIO SOBRE LAS 4 TABLAS DE LA RESERVACIÓN. El portal dejaría de poder capturar.
--  Verifícalo antes:
--      SHOW COLUMNS FROM `rsv_reservacion` LIKE 'reservacionMaestro';   -- debe devolver 1 fila
--
--  IDEMPOTENCIA
--  ------------
--  Todo objeto se recrea con DROP ... IF EXISTS antes de CREATE. El script se puede
--  ejecutar varias veces sin error y no borra datos.
--
--  CÓMO EJECUTAR
--  -------------
--    * mysql -u USUARIO -p asturias_api < 2026_08_18_triggers_rsv_replicacion_maestria.sql
--    * HeidiSQL / DBeaver / Workbench: abrir y ejecutar todo.
--    * phpMyAdmin: pestaña SQL; respeta las líneas DELIMITER $$ / DELIMITER ;
--
--  COSTE
--  -----
--  La guarda añade UN `SELECT` por clave primaria: un index dive, microsegundos. El
--  índice `idx_reservacion_maestro` de la migración lo cubre.
--
--  LIMITACIÓN CONOCIDA (heredada, sin cambios)
--  -------------------------------------------
--  En los triggers nieto se resuelve la reservación padre con un SELECT sobre
--  `rsv_reservacionhabitacion`. Si por un borrado en orden atípico la habitación ya no
--  existiera, la búsqueda devuelve NULL y el procedimiento lo ignora. En el flujo real
--  los hijos se borran ANTES que su habitación, y el borrado de la habitación marca
--  igualmente la reservación.
-- =============================================================================


-- -----------------------------------------------------------------------------
-- 0. Contexto de sesión
-- -----------------------------------------------------------------------------
-- Descomenta y ajusta si tu cliente no está posicionado en el esquema:
-- USE `asturias_api`;


-- -----------------------------------------------------------------------------
-- 1. Procedimiento de marca — CON LA GUARDA DE MAESTRÍA (el único cambio real)
--    Reactiva la marca abierta de la reservación o inserta una nueva.
-- -----------------------------------------------------------------------------
DROP PROCEDURE IF EXISTS `sp_rsv_marcar_replicacion`;

DELIMITER $$
CREATE PROCEDURE `sp_rsv_marcar_replicacion`(
    IN p_idReservacion INT,
    IN p_operacion     VARCHAR(20)
)
BEGIN
    DECLARE v_maestro VARCHAR(10) DEFAULT NULL;

    IF p_idReservacion IS NOT NULL THEN

        -- FRONTERA DE MAESTRIA (una sola condicion, un solo lugar).
        --
        -- Los 12 triggers NO cambian: la regla vive aqui porque los triggers nieto
        -- (paquete/extra) tendrian que saltar dos tablas solo para leer un valor, y
        -- serian 12 sitios donde la regla puede divergir.
        --
        -- NULL (la reservacion ya no existe) tampoco marca. Es correcto y mas seguro
        -- que antes: el AFTER DELETE de la cabecera dejaba una marca que
        -- ReplicacionController::recolectar() cerraba igualmente sin replicar nada.
        SELECT `reservacionMaestro` INTO v_maestro
          FROM `rsv_reservacion`
         WHERE `idReservacion` = p_idReservacion
         LIMIT 1;

        IF v_maestro = 'Portal' THEN

            IF EXISTS (
                SELECT 1 FROM `rsv_replicacion`
                 WHERE `idReservacion` = p_idReservacion
                   AND `replicacionEstado` IN ('Pendiente', 'Error')
            ) THEN
                UPDATE `rsv_replicacion`
                   SET `replicacionOperacion` = p_operacion,
                       `replicacionEstado`    = 'Pendiente',
                       `replicacionFecha`     = NOW(),
                       `replicacionError`     = NULL
                 WHERE `idReservacion` = p_idReservacion
                   AND `replicacionEstado` IN ('Pendiente', 'Error');
            ELSE
                INSERT INTO `rsv_replicacion`
                    (`idReservacion`, `replicacionOperacion`, `replicacionEstado`,
                     `replicacionIntentos`, `replicacionFecha`)
                VALUES
                    (p_idReservacion, p_operacion, 'Pendiente', 0, NOW());
            END IF;

        END IF;
    END IF;
END$$
DELIMITER ;


-- -----------------------------------------------------------------------------
-- 2. Triggers sobre rsv_reservacion (cabecera) — SIN CAMBIOS respecto al 24-jul
--    INSERT/UPDATE → 'Actualizacion'; DELETE → 'Eliminacion'.
-- -----------------------------------------------------------------------------
DROP TRIGGER IF EXISTS `trg_rsv_reservacion_ai`;
DROP TRIGGER IF EXISTS `trg_rsv_reservacion_au`;
DROP TRIGGER IF EXISTS `trg_rsv_reservacion_ad`;

DELIMITER $$

CREATE TRIGGER `trg_rsv_reservacion_ai` AFTER INSERT ON `rsv_reservacion`
FOR EACH ROW
BEGIN
    CALL sp_rsv_marcar_replicacion(NEW.`idReservacion`, 'Actualizacion');
END$$

CREATE TRIGGER `trg_rsv_reservacion_au` AFTER UPDATE ON `rsv_reservacion`
FOR EACH ROW
BEGIN
    CALL sp_rsv_marcar_replicacion(NEW.`idReservacion`, 'Actualizacion');
END$$

CREATE TRIGGER `trg_rsv_reservacion_ad` AFTER DELETE ON `rsv_reservacion`
FOR EACH ROW
BEGIN
    CALL sp_rsv_marcar_replicacion(OLD.`idReservacion`, 'Eliminacion');
END$$

DELIMITER ;


-- -----------------------------------------------------------------------------
-- 3. Triggers sobre rsv_reservacionhabitacion — SIN CAMBIOS
-- -----------------------------------------------------------------------------
DROP TRIGGER IF EXISTS `trg_rsv_rhabitacion_ai`;
DROP TRIGGER IF EXISTS `trg_rsv_rhabitacion_au`;
DROP TRIGGER IF EXISTS `trg_rsv_rhabitacion_ad`;

DELIMITER $$

CREATE TRIGGER `trg_rsv_rhabitacion_ai` AFTER INSERT ON `rsv_reservacionhabitacion`
FOR EACH ROW
BEGIN
    CALL sp_rsv_marcar_replicacion(NEW.`idReservacion`, 'Actualizacion');
END$$

CREATE TRIGGER `trg_rsv_rhabitacion_au` AFTER UPDATE ON `rsv_reservacionhabitacion`
FOR EACH ROW
BEGIN
    CALL sp_rsv_marcar_replicacion(NEW.`idReservacion`, 'Actualizacion');
END$$

CREATE TRIGGER `trg_rsv_rhabitacion_ad` AFTER DELETE ON `rsv_reservacionhabitacion`
FOR EACH ROW
BEGIN
    CALL sp_rsv_marcar_replicacion(OLD.`idReservacion`, 'Actualizacion');
END$$

DELIMITER ;


-- -----------------------------------------------------------------------------
-- 4. Triggers sobre rsv_reservacionhabitacionpaquete — SIN CAMBIOS
--    Resuelve la reservación padre vía idReservacionHabitacion.
-- -----------------------------------------------------------------------------
DROP TRIGGER IF EXISTS `trg_rsv_rhpaquete_ai`;
DROP TRIGGER IF EXISTS `trg_rsv_rhpaquete_au`;
DROP TRIGGER IF EXISTS `trg_rsv_rhpaquete_ad`;

DELIMITER $$

CREATE TRIGGER `trg_rsv_rhpaquete_ai` AFTER INSERT ON `rsv_reservacionhabitacionpaquete`
FOR EACH ROW
BEGIN
    DECLARE v_idReservacion INT DEFAULT NULL;
    SELECT `idReservacion` INTO v_idReservacion
      FROM `rsv_reservacionhabitacion`
     WHERE `idReservacionHabitacion` = NEW.`idReservacionHabitacion`
     LIMIT 1;
    CALL sp_rsv_marcar_replicacion(v_idReservacion, 'Actualizacion');
END$$

CREATE TRIGGER `trg_rsv_rhpaquete_au` AFTER UPDATE ON `rsv_reservacionhabitacionpaquete`
FOR EACH ROW
BEGIN
    DECLARE v_idReservacion INT DEFAULT NULL;
    SELECT `idReservacion` INTO v_idReservacion
      FROM `rsv_reservacionhabitacion`
     WHERE `idReservacionHabitacion` = NEW.`idReservacionHabitacion`
     LIMIT 1;
    CALL sp_rsv_marcar_replicacion(v_idReservacion, 'Actualizacion');
END$$

CREATE TRIGGER `trg_rsv_rhpaquete_ad` AFTER DELETE ON `rsv_reservacionhabitacionpaquete`
FOR EACH ROW
BEGIN
    DECLARE v_idReservacion INT DEFAULT NULL;
    SELECT `idReservacion` INTO v_idReservacion
      FROM `rsv_reservacionhabitacion`
     WHERE `idReservacionHabitacion` = OLD.`idReservacionHabitacion`
     LIMIT 1;
    CALL sp_rsv_marcar_replicacion(v_idReservacion, 'Actualizacion');
END$$

DELIMITER ;


-- -----------------------------------------------------------------------------
-- 5. Triggers sobre rsv_reservacionhabitacionextra — SIN CAMBIOS
--    Resuelve la reservación padre vía idReservacionHabitacion.
-- -----------------------------------------------------------------------------
DROP TRIGGER IF EXISTS `trg_rsv_rhextra_ai`;
DROP TRIGGER IF EXISTS `trg_rsv_rhextra_au`;
DROP TRIGGER IF EXISTS `trg_rsv_rhextra_ad`;

DELIMITER $$

CREATE TRIGGER `trg_rsv_rhextra_ai` AFTER INSERT ON `rsv_reservacionhabitacionextra`
FOR EACH ROW
BEGIN
    DECLARE v_idReservacion INT DEFAULT NULL;
    SELECT `idReservacion` INTO v_idReservacion
      FROM `rsv_reservacionhabitacion`
     WHERE `idReservacionHabitacion` = NEW.`idReservacionHabitacion`
     LIMIT 1;
    CALL sp_rsv_marcar_replicacion(v_idReservacion, 'Actualizacion');
END$$

CREATE TRIGGER `trg_rsv_rhextra_au` AFTER UPDATE ON `rsv_reservacionhabitacionextra`
FOR EACH ROW
BEGIN
    DECLARE v_idReservacion INT DEFAULT NULL;
    SELECT `idReservacion` INTO v_idReservacion
      FROM `rsv_reservacionhabitacion`
     WHERE `idReservacionHabitacion` = NEW.`idReservacionHabitacion`
     LIMIT 1;
    CALL sp_rsv_marcar_replicacion(v_idReservacion, 'Actualizacion');
END$$

CREATE TRIGGER `trg_rsv_rhextra_ad` AFTER DELETE ON `rsv_reservacionhabitacionextra`
FOR EACH ROW
BEGIN
    DECLARE v_idReservacion INT DEFAULT NULL;
    SELECT `idReservacion` INTO v_idReservacion
      FROM `rsv_reservacionhabitacion`
     WHERE `idReservacionHabitacion` = OLD.`idReservacionHabitacion`
     LIMIT 1;
    CALL sp_rsv_marcar_replicacion(v_idReservacion, 'Actualizacion');
END$$

DELIMITER ;


-- =============================================================================
--  6. VERIFICACIÓN (ejecutar tras instalar)
-- =============================================================================
--  6.1 El procedimiento existe y los 12 triggers siguen ahí:
--
--      SHOW PROCEDURE STATUS WHERE Db = DATABASE() AND Name = 'sp_rsv_marcar_replicacion';
--
--      SELECT COUNT(*) AS debe_ser_12 FROM information_schema.TRIGGERS
--       WHERE TRIGGER_SCHEMA = DATABASE()
--         AND EVENT_OBJECT_TABLE LIKE 'rsv_reservacion%';
--
--  6.2 PRUEBA DEL CORTE — una reservación ya entregada al privado NO debe marcar.
--      Es LA prueba de esta fase: se toca la fila DIRECTO EN LA BD, sin pasar por la
--      app, que es justo lo que la capa de aplicación no puede cubrir.
--
--      SET @idr := (SELECT idReservacion FROM rsv_reservacion
--                    WHERE reservacionMaestro = 'Privado'
--                    ORDER BY idReservacion DESC LIMIT 1);
--      SELECT COUNT(*) INTO @antes FROM rsv_replicacion WHERE idReservacion = @idr;
--      UPDATE rsv_reservacion
--         SET reservacionNotas = CONCAT(IFNULL(reservacionNotas,''),' ')
--       WHERE idReservacion = @idr;
--      SELECT COUNT(*) = @antes AS debe_ser_1
--        FROM rsv_replicacion WHERE idReservacion = @idr;
--
--  6.3 PRUEBA DE QUE NO SE CORTÓ DE MÁS — una Temporal SÍ debe marcar.
--      Si esto falla, el portal dejó de replicar holds y Reservaciones V2 no descuenta cupo:
--      es el camino directo a la sobreventa.
--
--      SET @idt := (SELECT idReservacion FROM rsv_reservacion
--                    WHERE reservacionEstado = 'Temporal' AND reservacionMaestro = 'Portal'
--                    ORDER BY idReservacion DESC LIMIT 1);
--      UPDATE rsv_reservacion
--         SET reservacionNotas = CONCAT(IFNULL(reservacionNotas,''),' ')
--       WHERE idReservacion = @idt;
--      SELECT * FROM rsv_replicacion
--       WHERE idReservacion = @idt AND replicacionEstado = 'Pendiente';   -- debe haber 1
-- =============================================================================


-- =============================================================================
--  7. REVERSA (rollback)
-- =============================================================================
--  Para volver al comportamiento anterior SIN quitar la captura, basta con reinstalar
--  el script previo, que recrea el procedimiento sin la guarda:
--      database/manual/2026_07_24_triggers_rsv_replicacion.sql
--  Las columnas de maestría pueden quedarse: son inertes sin esta guarda.
--
--  Para desinstalar la Capa 2 por completo, descomentar:
-- DROP TRIGGER IF EXISTS `trg_rsv_reservacion_ai`;
-- DROP TRIGGER IF EXISTS `trg_rsv_reservacion_au`;
-- DROP TRIGGER IF EXISTS `trg_rsv_reservacion_ad`;
-- DROP TRIGGER IF EXISTS `trg_rsv_rhabitacion_ai`;
-- DROP TRIGGER IF EXISTS `trg_rsv_rhabitacion_au`;
-- DROP TRIGGER IF EXISTS `trg_rsv_rhabitacion_ad`;
-- DROP TRIGGER IF EXISTS `trg_rsv_rhpaquete_ai`;
-- DROP TRIGGER IF EXISTS `trg_rsv_rhpaquete_au`;
-- DROP TRIGGER IF EXISTS `trg_rsv_rhpaquete_ad`;
-- DROP TRIGGER IF EXISTS `trg_rsv_rhextra_ai`;
-- DROP TRIGGER IF EXISTS `trg_rsv_rhextra_au`;
-- DROP TRIGGER IF EXISTS `trg_rsv_rhextra_ad`;
-- DROP PROCEDURE IF EXISTS `sp_rsv_marcar_replicacion`;
-- =============================================================================
--  FIN DEL SCRIPT
-- =============================================================================
