-- =====================================================================
-- DB_VENTAS V2.0 - BASE DE DATOS MEJORADA
-- Compatible con MySQL 8.0+ / MariaDB 10.5+
-- Incluye estructura, restricciones, triggers, procedimientos,
-- vistas y datos semilla.
-- =====================================================================

DROP DATABASE IF EXISTS db_ventas;
CREATE DATABASE db_ventas
  CHARACTER SET utf8mb4
  COLLATE utf8mb4_unicode_ci;
USE db_ventas;


-- =====================================================================
-- MÓDULO 1: CONFIGURACIÓN / EMPRESA
-- =====================================================================
CREATE TABLE empresa (
    id_empresa INT AUTO_INCREMENT PRIMARY KEY,
    ruc VARCHAR(11) NOT NULL,
    razon_social VARCHAR(150) NOT NULL,
    nombre_comercial VARCHAR(150),
    direccion VARCHAR(200),
    telefono VARCHAR(20),
    email VARCHAR(100),
    estado ENUM('ACTIVO','INACTIVO') NOT NULL DEFAULT 'ACTIVO',
    fecha_registro DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    fecha_actualizacion DATETIME NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE KEY uk_empresa_ruc (ruc),
    CONSTRAINT chk_empresa_ruc CHECK (ruc REGEXP '^[0-9]{11}$')
) ENGINE=InnoDB;

-- =====================================================================
-- MÓDULO 2: SEGURIDAD
-- =====================================================================
CREATE TABLE roles (
    id_rol INT AUTO_INCREMENT PRIMARY KEY,
    nombre VARCHAR(50) NOT NULL,
    descripcion VARCHAR(150),
    es_sistema TINYINT(1) NOT NULL DEFAULT 0,
    estado ENUM('ACTIVO','INACTIVO') NOT NULL DEFAULT 'ACTIVO',
    fecha_registro DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY uk_roles_nombre (nombre),
    CONSTRAINT chk_roles_es_sistema CHECK (es_sistema IN (0,1))
) ENGINE=InnoDB;

CREATE TABLE usuarios (
    id_usuario INT AUTO_INCREMENT PRIMARY KEY,
    nombre VARCHAR(100) NOT NULL,
    email VARCHAR(100) NOT NULL,
    imagen VARCHAR(255) NOT NULL DEFAULT 'uploads/usuarios/usuario_default.png',
    password_hash VARCHAR(255) NOT NULL,
    estado ENUM('ACTIVO','INACTIVO') NOT NULL DEFAULT 'ACTIVO',
    fecha_registro DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    fecha_actualizacion DATETIME NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE KEY uk_usuarios_email (email)
) ENGINE=InnoDB;

CREATE TABLE usuario_roles (
    id_usuario_rol INT AUTO_INCREMENT PRIMARY KEY,
    id_usuario INT NOT NULL,
    id_rol INT NOT NULL,
    fecha_asignacion DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    CONSTRAINT fk_usuario_roles_usuario FOREIGN KEY (id_usuario)
        REFERENCES usuarios(id_usuario) ON UPDATE CASCADE ON DELETE CASCADE,
    CONSTRAINT fk_usuario_roles_rol FOREIGN KEY (id_rol)
        REFERENCES roles(id_rol) ON UPDATE CASCADE ON DELETE RESTRICT,
    UNIQUE KEY uk_usuario_roles (id_usuario, id_rol),
    KEY idx_usuario_roles_usuario (id_usuario),
    KEY idx_usuario_roles_rol (id_rol)
) ENGINE=InnoDB;

CREATE TABLE modulos (
    id_modulo INT AUTO_INCREMENT PRIMARY KEY,
    nombre VARCHAR(80) NOT NULL,
    ruta VARCHAR(100) NOT NULL,
    icono VARCHAR(60) NULL,
    orden_menu INT NOT NULL DEFAULT 0,
    estado ENUM('ACTIVO','INACTIVO') NOT NULL DEFAULT 'ACTIVO',
    UNIQUE KEY uk_modulos_nombre (nombre),
    UNIQUE KEY uk_modulos_ruta (ruta),
    CONSTRAINT chk_modulos_orden CHECK (orden_menu >= 0)
) ENGINE=InnoDB;

CREATE TABLE permisos (
    id_permiso INT AUTO_INCREMENT PRIMARY KEY,
    id_modulo INT NOT NULL,
    nombre VARCHAR(50) NOT NULL,
    descripcion VARCHAR(150),
    estado ENUM('ACTIVO','INACTIVO') NOT NULL DEFAULT 'ACTIVO',
    CONSTRAINT fk_permisos_modulos FOREIGN KEY (id_modulo)
        REFERENCES modulos(id_modulo) ON UPDATE CASCADE ON DELETE RESTRICT,
    UNIQUE KEY uk_permisos_modulo_nombre (id_modulo, nombre),
    KEY idx_permisos_modulo (id_modulo)
) ENGINE=InnoDB;

CREATE TABLE rol_permisos (
    id_rol_permiso INT AUTO_INCREMENT PRIMARY KEY,
    id_rol INT NOT NULL,
    id_permiso INT NOT NULL,
    fecha_asignacion DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    CONSTRAINT fk_rol_permisos_rol FOREIGN KEY (id_rol)
        REFERENCES roles(id_rol) ON UPDATE CASCADE ON DELETE CASCADE,
    CONSTRAINT fk_rol_permisos_permiso FOREIGN KEY (id_permiso)
        REFERENCES permisos(id_permiso) ON UPDATE CASCADE ON DELETE CASCADE,
    UNIQUE KEY uk_rol_permisos (id_rol, id_permiso),
    KEY idx_rol_permisos_rol (id_rol),
    KEY idx_rol_permisos_permiso (id_permiso)
) ENGINE=InnoDB;

CREATE TABLE sesiones (
    id_sesion BIGINT AUTO_INCREMENT PRIMARY KEY,
    id_usuario INT NOT NULL,
    token_hash CHAR(64) NOT NULL,
    ip VARCHAR(45),
    navegador VARCHAR(255),
    fecha_inicio DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    fecha_ultima_actividad DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    fecha_fin DATETIME NULL,
    estado ENUM('ACTIVA','CERRADA','EXPIRADA') NOT NULL DEFAULT 'ACTIVA',
    CONSTRAINT fk_sesiones_usuario FOREIGN KEY (id_usuario)
        REFERENCES usuarios(id_usuario) ON UPDATE CASCADE ON DELETE CASCADE,
    UNIQUE KEY uk_sesiones_token_hash (token_hash),
    KEY idx_sesiones_usuario_estado (id_usuario, estado),
    KEY idx_sesiones_actividad (fecha_ultima_actividad)
) ENGINE=InnoDB;

-- =====================================================================
-- MÓDULO 3: CLIENTES
-- =====================================================================
CREATE TABLE clientes (
    id_cliente INT AUTO_INCREMENT PRIMARY KEY,
    tipo_documento ENUM('DNI','RUC','CE') NOT NULL,
    numero_documento VARCHAR(20) NOT NULL,
    nombre VARCHAR(150) NOT NULL,
    direccion VARCHAR(200),
    telefono VARCHAR(20),
    email VARCHAR(100),
    estado ENUM('ACTIVO','INACTIVO') NOT NULL DEFAULT 'ACTIVO',
    fecha_registro DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    fecha_actualizacion DATETIME NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE KEY uk_clientes_documento (tipo_documento, numero_documento),
    KEY idx_clientes_nombre (nombre),
    CONSTRAINT chk_clientes_documento CHECK (
        (tipo_documento = 'DNI' AND numero_documento REGEXP '^[0-9]{8}$') OR
        (tipo_documento = 'RUC' AND numero_documento REGEXP '^[0-9]{11}$') OR
        (tipo_documento = 'CE' AND CHAR_LENGTH(numero_documento) BETWEEN 8 AND 20)
    )
) ENGINE=InnoDB;

-- =====================================================================
-- MÓDULO 4: PROVEEDORES
-- =====================================================================
CREATE TABLE proveedores (
    id_proveedor INT AUTO_INCREMENT PRIMARY KEY,
    ruc VARCHAR(11) NOT NULL,
    razon_social VARCHAR(150) NOT NULL,
    telefono VARCHAR(20),
    email VARCHAR(100),
    direccion VARCHAR(200),
    estado ENUM('ACTIVO','INACTIVO') NOT NULL DEFAULT 'ACTIVO',
    fecha_registro DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    fecha_actualizacion DATETIME NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE KEY uk_proveedores_ruc (ruc),
    KEY idx_proveedores_razon_social (razon_social),
    CONSTRAINT chk_proveedores_ruc CHECK (ruc REGEXP '^[0-9]{11}$')
) ENGINE=InnoDB;

-- =====================================================================
-- MÓDULO 5: CATÁLOGO / PRODUCTOS
-- =====================================================================
CREATE TABLE categorias (
    id_categoria INT AUTO_INCREMENT PRIMARY KEY,
    nombre VARCHAR(100) NOT NULL,
    descripcion VARCHAR(200),
    estado ENUM('ACTIVO','INACTIVO') NOT NULL DEFAULT 'ACTIVO',
    UNIQUE KEY uk_categorias_nombre (nombre)
) ENGINE=InnoDB;

CREATE TABLE marcas (
    id_marca INT AUTO_INCREMENT PRIMARY KEY,
    nombre VARCHAR(100) NOT NULL,
    descripcion VARCHAR(200),
    estado ENUM('ACTIVO','INACTIVO') NOT NULL DEFAULT 'ACTIVO',
    UNIQUE KEY uk_marcas_nombre (nombre)
) ENGINE=InnoDB;

CREATE TABLE productos (
    id_producto INT AUTO_INCREMENT PRIMARY KEY,
    id_categoria INT NOT NULL,
    id_marca INT NOT NULL,
    codigo VARCHAR(30) NOT NULL,
    nombre VARCHAR(120) NOT NULL,
    descripcion TEXT,
    imagen VARCHAR(255) NOT NULL DEFAULT 'uploads/productos/producto_default.png',
    precio_compra DECIMAL(12,2) NOT NULL DEFAULT 0.00,
    precio_venta DECIMAL(12,2) NOT NULL DEFAULT 0.00,
    stock_minimo INT NOT NULL DEFAULT 0,
    estado ENUM('ACTIVO','INACTIVO') NOT NULL DEFAULT 'ACTIVO',
    fecha_registro DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    fecha_actualizacion DATETIME NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
    CONSTRAINT fk_productos_categoria FOREIGN KEY (id_categoria)
        REFERENCES categorias(id_categoria) ON UPDATE CASCADE ON DELETE RESTRICT,
    CONSTRAINT fk_productos_marca FOREIGN KEY (id_marca)
        REFERENCES marcas(id_marca) ON UPDATE CASCADE ON DELETE RESTRICT,
    UNIQUE KEY uk_productos_codigo (codigo),
    KEY idx_productos_nombre (nombre),
    KEY idx_productos_categoria (id_categoria),
    KEY idx_productos_marca (id_marca),
    CONSTRAINT chk_productos_precio_compra CHECK (precio_compra >= 0),
    CONSTRAINT chk_productos_precio_venta CHECK (precio_venta >= 0),
    CONSTRAINT chk_productos_stock_minimo CHECK (stock_minimo >= 0)
) ENGINE=InnoDB;

-- =====================================================================
-- MÓDULO 6: COMPROBANTES Y MÉTODOS DE PAGO
-- =====================================================================
CREATE TABLE tipos_comprobante (
    id_tipo_comprobante INT AUTO_INCREMENT PRIMARY KEY,
    codigo VARCHAR(20) NOT NULL,
    nombre VARCHAR(80) NOT NULL,
    tipo ENUM('VENTA','COMPRA') NOT NULL,
    estado ENUM('ACTIVO','INACTIVO') NOT NULL DEFAULT 'ACTIVO',
    UNIQUE KEY uk_tipos_comprobante_codigo (codigo),
    UNIQUE KEY uk_tipos_comprobante_nombre_tipo (nombre, tipo)
) ENGINE=InnoDB;

CREATE TABLE series_comprobantes (
    id_serie INT AUTO_INCREMENT PRIMARY KEY,
    id_tipo_comprobante INT NOT NULL,
    serie VARCHAR(10) NOT NULL,
    numero_actual BIGINT NOT NULL DEFAULT 0,
    estado ENUM('ACTIVO','INACTIVO') NOT NULL DEFAULT 'ACTIVO',
    CONSTRAINT fk_series_tipo_comprobante FOREIGN KEY (id_tipo_comprobante)
        REFERENCES tipos_comprobante(id_tipo_comprobante) ON UPDATE CASCADE ON DELETE RESTRICT,
    UNIQUE KEY uk_series_tipo_serie (id_tipo_comprobante, serie),
    KEY idx_series_tipo_comprobante (id_tipo_comprobante),
    CONSTRAINT chk_series_numero_actual CHECK (numero_actual >= 0)
) ENGINE=InnoDB;

CREATE TABLE metodos_pago (
    id_metodo_pago INT AUTO_INCREMENT PRIMARY KEY,
    nombre VARCHAR(50) NOT NULL,
    descripcion VARCHAR(150),
    requiere_referencia TINYINT(1) NOT NULL DEFAULT 0,
    estado ENUM('ACTIVO','INACTIVO') NOT NULL DEFAULT 'ACTIVO',
    UNIQUE KEY uk_metodos_pago_nombre (nombre),
    CONSTRAINT chk_metodos_referencia CHECK (requiere_referencia IN (0,1))
) ENGINE=InnoDB;

-- =====================================================================
-- MÓDULO 7: INVENTARIO
-- =====================================================================
CREATE TABLE almacenes (
    id_almacen INT AUTO_INCREMENT PRIMARY KEY,
    nombre VARCHAR(100) NOT NULL,
    direccion VARCHAR(200),
    estado ENUM('ACTIVO','INACTIVO') NOT NULL DEFAULT 'ACTIVO',
    UNIQUE KEY uk_almacenes_nombre (nombre)
) ENGINE=InnoDB;

CREATE TABLE stock_almacen (
    id_stock INT AUTO_INCREMENT PRIMARY KEY,
    id_almacen INT NOT NULL,
    id_producto INT NOT NULL,
    stock_actual INT NOT NULL DEFAULT 0,
    fecha_actualizacion DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    CONSTRAINT fk_stock_almacen_almacen FOREIGN KEY (id_almacen)
        REFERENCES almacenes(id_almacen) ON UPDATE CASCADE ON DELETE RESTRICT,
    CONSTRAINT fk_stock_almacen_producto FOREIGN KEY (id_producto)
        REFERENCES productos(id_producto) ON UPDATE CASCADE ON DELETE RESTRICT,
    UNIQUE KEY uk_stock_almacen_producto (id_almacen, id_producto),
    KEY idx_stock_producto (id_producto),
    CONSTRAINT chk_stock_actual CHECK (stock_actual >= 0)
) ENGINE=InnoDB;

CREATE TABLE movimientos_stock (
    id_movimiento_stock BIGINT AUTO_INCREMENT PRIMARY KEY,
    id_producto INT NOT NULL,
    id_almacen INT NOT NULL,
    id_almacen_destino INT NULL,
    id_usuario INT NOT NULL,
    tipo_movimiento ENUM('ENTRADA','SALIDA','AJUSTE','TRASLADO') NOT NULL,
    cantidad INT NOT NULL,
    motivo VARCHAR(150) NOT NULL,
    referencia_tipo ENUM('COMPRA','VENTA','AJUSTE','TRASLADO','ANULACION','INICIAL','OTRO') NOT NULL DEFAULT 'OTRO',
    referencia_id BIGINT NULL,
    referencia VARCHAR(100),
    fecha DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    CONSTRAINT fk_mov_stock_producto FOREIGN KEY (id_producto)
        REFERENCES productos(id_producto) ON UPDATE CASCADE ON DELETE RESTRICT,
    CONSTRAINT fk_mov_stock_almacen FOREIGN KEY (id_almacen)
        REFERENCES almacenes(id_almacen) ON UPDATE CASCADE ON DELETE RESTRICT,
    CONSTRAINT fk_mov_stock_almacen_destino FOREIGN KEY (id_almacen_destino)
        REFERENCES almacenes(id_almacen) ON UPDATE CASCADE ON DELETE RESTRICT,
    CONSTRAINT fk_mov_stock_usuario FOREIGN KEY (id_usuario)
        REFERENCES usuarios(id_usuario) ON UPDATE CASCADE ON DELETE RESTRICT,
    KEY idx_mov_stock_producto_fecha (id_producto, fecha),
    KEY idx_mov_stock_almacen_fecha (id_almacen, fecha),
    KEY idx_mov_stock_referencia (referencia_tipo, referencia_id),
    CONSTRAINT chk_mov_stock_cantidad CHECK (cantidad > 0),
    CONSTRAINT chk_mov_stock_traslado CHECK (
        (tipo_movimiento <> 'TRASLADO' AND id_almacen_destino IS NULL) OR
        (tipo_movimiento = 'TRASLADO' AND id_almacen_destino IS NOT NULL AND id_almacen_destino <> id_almacen)
    )
) ENGINE=InnoDB;

CREATE TABLE kardex (
    id_kardex BIGINT AUTO_INCREMENT PRIMARY KEY,
    id_producto INT NOT NULL,
    id_almacen INT NOT NULL,
    id_usuario INT NULL,
    fecha DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    tipo_movimiento ENUM('ENTRADA','SALIDA','AJUSTE','TRASLADO') NOT NULL,
    referencia_tipo ENUM('COMPRA','VENTA','AJUSTE','TRASLADO','ANULACION','INICIAL','OTRO') NOT NULL DEFAULT 'OTRO',
    referencia_id BIGINT NULL,
    cantidad INT NOT NULL,
    costo_unitario DECIMAL(12,2) NOT NULL DEFAULT 0.00,
    stock_anterior INT NOT NULL,
    stock_actual INT NOT NULL,
    saldo_valorizado DECIMAL(14,2) NOT NULL DEFAULT 0.00,
    observacion VARCHAR(200),
    CONSTRAINT fk_kardex_producto FOREIGN KEY (id_producto)
        REFERENCES productos(id_producto) ON UPDATE CASCADE ON DELETE RESTRICT,
    CONSTRAINT fk_kardex_almacen FOREIGN KEY (id_almacen)
        REFERENCES almacenes(id_almacen) ON UPDATE CASCADE ON DELETE RESTRICT,
    CONSTRAINT fk_kardex_usuario FOREIGN KEY (id_usuario)
        REFERENCES usuarios(id_usuario) ON UPDATE CASCADE ON DELETE SET NULL,
    KEY idx_kardex_producto_almacen_fecha (id_producto, id_almacen, fecha),
    KEY idx_kardex_referencia (referencia_tipo, referencia_id),
    CONSTRAINT chk_kardex_cantidad CHECK (cantidad > 0),
    CONSTRAINT chk_kardex_costo_unitario CHECK (costo_unitario >= 0),
    CONSTRAINT chk_kardex_stock_anterior CHECK (stock_anterior >= 0),
    CONSTRAINT chk_kardex_stock_actual CHECK (stock_actual >= 0),
    CONSTRAINT chk_kardex_saldo_valorizado CHECK (saldo_valorizado >= 0)
) ENGINE=InnoDB;

-- =====================================================================
-- MÓDULO 8: COMPRAS
-- =====================================================================
CREATE TABLE compras (
    id_compra BIGINT AUTO_INCREMENT PRIMARY KEY,
    id_proveedor INT NOT NULL,
    id_usuario INT NOT NULL,
    id_almacen INT NOT NULL,
    id_tipo_comprobante INT NOT NULL,
    serie VARCHAR(10) NOT NULL,
    numero VARCHAR(20) NOT NULL,
    fecha DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    subtotal DECIMAL(12,2) NOT NULL DEFAULT 0.00,
    igv DECIMAL(12,2) NOT NULL DEFAULT 0.00,
    total DECIMAL(12,2) NOT NULL DEFAULT 0.00,
    estado ENUM('REGISTRADA','ANULADA') NOT NULL DEFAULT 'REGISTRADA',
    fecha_anulacion DATETIME NULL,
    motivo_anulacion VARCHAR(200) NULL,
    CONSTRAINT fk_compras_proveedor FOREIGN KEY (id_proveedor)
        REFERENCES proveedores(id_proveedor) ON UPDATE CASCADE ON DELETE RESTRICT,
    CONSTRAINT fk_compras_usuario FOREIGN KEY (id_usuario)
        REFERENCES usuarios(id_usuario) ON UPDATE CASCADE ON DELETE RESTRICT,
    CONSTRAINT fk_compras_almacen FOREIGN KEY (id_almacen)
        REFERENCES almacenes(id_almacen) ON UPDATE CASCADE ON DELETE RESTRICT,
    CONSTRAINT fk_compras_tipo_comprobante FOREIGN KEY (id_tipo_comprobante)
        REFERENCES tipos_comprobante(id_tipo_comprobante) ON UPDATE CASCADE ON DELETE RESTRICT,
    UNIQUE KEY uk_compras_comprobante (id_tipo_comprobante, serie, numero),
    KEY idx_compras_fecha (fecha),
    KEY idx_compras_proveedor (id_proveedor),
    KEY idx_compras_almacen (id_almacen),
    CONSTRAINT chk_compras_subtotal CHECK (subtotal >= 0),
    CONSTRAINT chk_compras_igv CHECK (igv >= 0),
    CONSTRAINT chk_compras_total CHECK (total >= 0),
    CONSTRAINT chk_compras_total_calculado CHECK (ABS(total - (subtotal + igv)) < 0.01)
) ENGINE=InnoDB;

CREATE TABLE detalle_compras (
    id_detalle_compra BIGINT AUTO_INCREMENT PRIMARY KEY,
    id_compra BIGINT NOT NULL,
    id_producto INT NOT NULL,
    cantidad INT NOT NULL,
    costo_unitario DECIMAL(12,2) NOT NULL,
    subtotal DECIMAL(12,2) NOT NULL,
    CONSTRAINT fk_detalle_compras_compra FOREIGN KEY (id_compra)
        REFERENCES compras(id_compra) ON UPDATE CASCADE ON DELETE RESTRICT,
    CONSTRAINT fk_detalle_compras_producto FOREIGN KEY (id_producto)
        REFERENCES productos(id_producto) ON UPDATE CASCADE ON DELETE RESTRICT,
    UNIQUE KEY uk_detalle_compras_producto (id_compra, id_producto),
    KEY idx_det_compras_producto (id_producto),
    CONSTRAINT chk_det_compras_cantidad CHECK (cantidad > 0),
    CONSTRAINT chk_det_compras_costo CHECK (costo_unitario >= 0),
    CONSTRAINT chk_det_compras_subtotal CHECK (ABS(subtotal - (cantidad * costo_unitario)) < 0.01)
) ENGINE=InnoDB;

-- =====================================================================
-- MÓDULO 9: VENTAS
-- =====================================================================
CREATE TABLE ventas (
    id_venta BIGINT AUTO_INCREMENT PRIMARY KEY,
    id_cliente INT NOT NULL,
    id_usuario INT NOT NULL,
    id_almacen INT NOT NULL,
    id_tipo_comprobante INT NOT NULL,
    serie VARCHAR(10) NOT NULL,
    numero VARCHAR(20) NOT NULL,
    fecha DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    subtotal DECIMAL(12,2) NOT NULL DEFAULT 0.00,
    igv DECIMAL(12,2) NOT NULL DEFAULT 0.00,
    total DECIMAL(12,2) NOT NULL DEFAULT 0.00,
    estado ENUM('EMITIDA','ANULADA') NOT NULL DEFAULT 'EMITIDA',
    estado_pago ENUM('PENDIENTE','PARCIAL','PAGADO') NOT NULL DEFAULT 'PENDIENTE',
    fecha_anulacion DATETIME NULL,
    motivo_anulacion VARCHAR(200) NULL,
    CONSTRAINT fk_ventas_cliente FOREIGN KEY (id_cliente)
        REFERENCES clientes(id_cliente) ON UPDATE CASCADE ON DELETE RESTRICT,
    CONSTRAINT fk_ventas_usuario FOREIGN KEY (id_usuario)
        REFERENCES usuarios(id_usuario) ON UPDATE CASCADE ON DELETE RESTRICT,
    CONSTRAINT fk_ventas_almacen FOREIGN KEY (id_almacen)
        REFERENCES almacenes(id_almacen) ON UPDATE CASCADE ON DELETE RESTRICT,
    CONSTRAINT fk_ventas_tipo_comprobante FOREIGN KEY (id_tipo_comprobante)
        REFERENCES tipos_comprobante(id_tipo_comprobante) ON UPDATE CASCADE ON DELETE RESTRICT,
    UNIQUE KEY uk_ventas_comprobante (id_tipo_comprobante, serie, numero),
    KEY idx_ventas_fecha (fecha),
    KEY idx_ventas_cliente (id_cliente),
    KEY idx_ventas_almacen (id_almacen),
    KEY idx_ventas_estado_pago (estado_pago),
    CONSTRAINT chk_ventas_subtotal CHECK (subtotal >= 0),
    CONSTRAINT chk_ventas_igv CHECK (igv >= 0),
    CONSTRAINT chk_ventas_total CHECK (total >= 0),
    CONSTRAINT chk_ventas_total_calculado CHECK (ABS(total - (subtotal + igv)) < 0.01)
) ENGINE=InnoDB;

CREATE TABLE detalle_ventas (
    id_detalle_venta BIGINT AUTO_INCREMENT PRIMARY KEY,
    id_venta BIGINT NOT NULL,
    id_producto INT NOT NULL,
    cantidad INT NOT NULL,
    costo_unitario DECIMAL(12,2) NOT NULL,
    precio_unitario DECIMAL(12,2) NOT NULL,
    subtotal DECIMAL(12,2) NOT NULL,
    CONSTRAINT fk_detalle_ventas_venta FOREIGN KEY (id_venta)
        REFERENCES ventas(id_venta) ON UPDATE CASCADE ON DELETE RESTRICT,
    CONSTRAINT fk_detalle_ventas_producto FOREIGN KEY (id_producto)
        REFERENCES productos(id_producto) ON UPDATE CASCADE ON DELETE RESTRICT,
    UNIQUE KEY uk_detalle_ventas_producto (id_venta, id_producto),
    KEY idx_det_ventas_producto (id_producto),
    CONSTRAINT chk_det_ventas_cantidad CHECK (cantidad > 0),
    CONSTRAINT chk_det_ventas_costo CHECK (costo_unitario >= 0),
    CONSTRAINT chk_det_ventas_precio CHECK (precio_unitario >= 0),
    CONSTRAINT chk_det_ventas_subtotal CHECK (ABS(subtotal - (cantidad * precio_unitario)) < 0.01)
) ENGINE=InnoDB;

-- =====================================================================
-- MÓDULO 10: CAJA Y PAGOS
-- =====================================================================
CREATE TABLE cajas (
    id_caja INT AUTO_INCREMENT PRIMARY KEY,
    nombre VARCHAR(100) NOT NULL,
    descripcion VARCHAR(150),
    estado ENUM('ACTIVA','INACTIVA') NOT NULL DEFAULT 'ACTIVA',
    UNIQUE KEY uk_cajas_nombre (nombre)
) ENGINE=InnoDB;

CREATE TABLE cierres_caja (
    id_cierre_caja BIGINT AUTO_INCREMENT PRIMARY KEY,
    id_caja INT NOT NULL,
    id_usuario_apertura INT NOT NULL,
    id_usuario_cierre INT NULL,
    fecha_apertura DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    fecha_cierre DATETIME NULL,
    saldo_inicial DECIMAL(12,2) NOT NULL DEFAULT 0.00,
    total_ingresos DECIMAL(12,2) NOT NULL DEFAULT 0.00,
    total_egresos DECIMAL(12,2) NOT NULL DEFAULT 0.00,
    saldo_sistema DECIMAL(12,2) NOT NULL DEFAULT 0.00,
    saldo_fisico DECIMAL(12,2) NULL,
    diferencia DECIMAL(12,2) NULL,
    estado ENUM('ABIERTA','CERRADA') NOT NULL DEFAULT 'ABIERTA',
    observacion VARCHAR(200),
    CONSTRAINT fk_cierres_caja_caja FOREIGN KEY (id_caja)
        REFERENCES cajas(id_caja) ON UPDATE CASCADE ON DELETE RESTRICT,
    CONSTRAINT fk_cierres_caja_usuario_apertura FOREIGN KEY (id_usuario_apertura)
        REFERENCES usuarios(id_usuario) ON UPDATE CASCADE ON DELETE RESTRICT,
    CONSTRAINT fk_cierres_caja_usuario_cierre FOREIGN KEY (id_usuario_cierre)
        REFERENCES usuarios(id_usuario) ON UPDATE CASCADE ON DELETE SET NULL,
    KEY idx_cierres_caja_estado (id_caja, estado),
    KEY idx_cierres_fecha_apertura (fecha_apertura),
    CONSTRAINT chk_cierres_saldo_inicial CHECK (saldo_inicial >= 0),
    CONSTRAINT chk_cierres_total_ingresos CHECK (total_ingresos >= 0),
    CONSTRAINT chk_cierres_total_egresos CHECK (total_egresos >= 0),
    CONSTRAINT chk_cierres_saldo_sistema CHECK (saldo_sistema >= 0)
) ENGINE=InnoDB;

CREATE TABLE pagos (
    id_pago BIGINT AUTO_INCREMENT PRIMARY KEY,
    id_venta BIGINT NOT NULL,
    id_metodo_pago INT NOT NULL,
    id_usuario INT NOT NULL,
    id_cierre_caja BIGINT NULL,
    fecha DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    monto DECIMAL(12,2) NOT NULL,
    referencia VARCHAR(100),
    observacion VARCHAR(200),
    estado ENUM('REGISTRADO','ANULADO') NOT NULL DEFAULT 'REGISTRADO',
    fecha_anulacion DATETIME NULL,
    CONSTRAINT fk_pagos_venta FOREIGN KEY (id_venta)
        REFERENCES ventas(id_venta) ON UPDATE CASCADE ON DELETE RESTRICT,
    CONSTRAINT fk_pagos_metodo_pago FOREIGN KEY (id_metodo_pago)
        REFERENCES metodos_pago(id_metodo_pago) ON UPDATE CASCADE ON DELETE RESTRICT,
    CONSTRAINT fk_pagos_usuario FOREIGN KEY (id_usuario)
        REFERENCES usuarios(id_usuario) ON UPDATE CASCADE ON DELETE RESTRICT,
    CONSTRAINT fk_pagos_cierre FOREIGN KEY (id_cierre_caja)
        REFERENCES cierres_caja(id_cierre_caja) ON UPDATE CASCADE ON DELETE RESTRICT,
    KEY idx_pagos_venta_estado (id_venta, estado),
    KEY idx_pagos_fecha (fecha),
    KEY idx_pagos_cierre (id_cierre_caja),
    CONSTRAINT chk_pagos_monto CHECK (monto > 0)
) ENGINE=InnoDB;

CREATE TABLE movimientos_caja (
    id_movimiento_caja BIGINT AUTO_INCREMENT PRIMARY KEY,
    id_cierre_caja BIGINT NOT NULL,
    id_caja INT NOT NULL,
    id_usuario INT NOT NULL,
    id_pago BIGINT NULL,
    tipo_movimiento ENUM('APERTURA','INGRESO','EGRESO','CIERRE','ANULACION') NOT NULL,
    monto DECIMAL(12,2) NOT NULL,
    descripcion VARCHAR(200),
    fecha DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    CONSTRAINT fk_mov_caja_cierre FOREIGN KEY (id_cierre_caja)
        REFERENCES cierres_caja(id_cierre_caja) ON UPDATE CASCADE ON DELETE RESTRICT,
    CONSTRAINT fk_mov_caja_caja FOREIGN KEY (id_caja)
        REFERENCES cajas(id_caja) ON UPDATE CASCADE ON DELETE RESTRICT,
    CONSTRAINT fk_mov_caja_usuario FOREIGN KEY (id_usuario)
        REFERENCES usuarios(id_usuario) ON UPDATE CASCADE ON DELETE RESTRICT,
    CONSTRAINT fk_mov_caja_pago FOREIGN KEY (id_pago)
        REFERENCES pagos(id_pago) ON UPDATE CASCADE ON DELETE SET NULL,
    KEY idx_mov_caja_cierre_fecha (id_cierre_caja, fecha),
    KEY idx_mov_caja_pago (id_pago),
    CONSTRAINT chk_mov_caja_monto CHECK (monto >= 0)
) ENGINE=InnoDB;

-- =====================================================================
-- MÓDULO 11: AUDITORÍA
-- =====================================================================
CREATE TABLE auditoria (
    id_auditoria BIGINT AUTO_INCREMENT PRIMARY KEY,
    id_usuario INT NULL,
    tabla_afectada VARCHAR(80) NOT NULL,
    registro_id BIGINT NULL,
    accion ENUM('INSERT','UPDATE','DELETE','ANULAR','LOGIN','LOGOUT') NOT NULL,
    valores_anteriores JSON NULL,
    valores_nuevos JSON NULL,
    descripcion TEXT,
    ip VARCHAR(45) NULL,
    fecha DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    CONSTRAINT fk_auditoria_usuario FOREIGN KEY (id_usuario)
        REFERENCES usuarios(id_usuario) ON UPDATE CASCADE ON DELETE SET NULL,
    KEY idx_auditoria_tabla_registro (tabla_afectada, registro_id),
    KEY idx_auditoria_usuario_fecha (id_usuario, fecha),
    KEY idx_auditoria_fecha (fecha)
) ENGINE=InnoDB;


-- =====================================================================
-- TRIGGERS Y REGLAS DE NEGOCIO
-- =====================================================================
DELIMITER $$

CREATE TRIGGER trg_roles_bu_proteger
BEFORE UPDATE ON roles
FOR EACH ROW
BEGIN
    IF OLD.es_sistema = 1 AND (NEW.nombre <> OLD.nombre OR NEW.es_sistema = 0) THEN
        SIGNAL SQLSTATE '45000'
            SET MESSAGE_TEXT = 'No se puede cambiar el nombre ni desproteger un rol del sistema';
    END IF;
END$$

CREATE TRIGGER trg_roles_bd_proteger
BEFORE DELETE ON roles
FOR EACH ROW
BEGIN
    IF OLD.es_sistema = 1 THEN
        SIGNAL SQLSTATE '45000'
            SET MESSAGE_TEXT = 'No se puede eliminar un rol protegido del sistema';
    END IF;
END$$

CREATE TRIGGER trg_cierres_caja_bi_unica_abierta
BEFORE INSERT ON cierres_caja
FOR EACH ROW
BEGIN
    IF NEW.estado = 'ABIERTA' AND EXISTS (
        SELECT 1 FROM cierres_caja
        WHERE id_caja = NEW.id_caja AND estado = 'ABIERTA'
    ) THEN
        SIGNAL SQLSTATE '45000'
            SET MESSAGE_TEXT = 'La caja ya tiene una apertura activa';
    END IF;
END$$

CREATE TRIGGER trg_detalle_compras_bi
BEFORE INSERT ON detalle_compras
FOR EACH ROW
BEGIN
    DECLARE v_estado VARCHAR(20);
    SELECT estado INTO v_estado FROM compras WHERE id_compra = NEW.id_compra;
    IF v_estado IS NULL THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'La compra no existe';
    END IF;
    IF v_estado <> 'REGISTRADA' THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'No se puede agregar detalle a una compra anulada';
    END IF;
    SET NEW.subtotal = ROUND(NEW.cantidad * NEW.costo_unitario, 2);
END$$

CREATE TRIGGER trg_detalle_compras_ai
AFTER INSERT ON detalle_compras
FOR EACH ROW
BEGIN
    DECLARE v_id_almacen INT;
    DECLARE v_id_usuario INT;
    DECLARE v_stock_anterior INT DEFAULT 0;
    DECLARE v_stock_actual INT DEFAULT 0;

    SELECT id_almacen, id_usuario
      INTO v_id_almacen, v_id_usuario
      FROM compras
     WHERE id_compra = NEW.id_compra;

    INSERT INTO stock_almacen (id_almacen, id_producto, stock_actual)
    VALUES (v_id_almacen, NEW.id_producto, 0)
    ON DUPLICATE KEY UPDATE stock_actual = stock_actual;

    SELECT stock_actual INTO v_stock_anterior
      FROM stock_almacen
     WHERE id_almacen = v_id_almacen AND id_producto = NEW.id_producto
     FOR UPDATE;

    UPDATE stock_almacen
       SET stock_actual = stock_actual + NEW.cantidad
     WHERE id_almacen = v_id_almacen AND id_producto = NEW.id_producto;

    SET v_stock_actual = v_stock_anterior + NEW.cantidad;

    INSERT INTO movimientos_stock
    (id_producto, id_almacen, id_usuario, tipo_movimiento, cantidad, motivo,
     referencia_tipo, referencia_id, referencia)
    VALUES
    (NEW.id_producto, v_id_almacen, v_id_usuario, 'ENTRADA', NEW.cantidad,
     'Compra registrada', 'COMPRA', NEW.id_compra, CONCAT('COMPRA-', NEW.id_compra));

    INSERT INTO kardex
    (id_producto, id_almacen, id_usuario, tipo_movimiento, referencia_tipo,
     referencia_id, cantidad, costo_unitario, stock_anterior, stock_actual,
     saldo_valorizado, observacion)
    VALUES
    (NEW.id_producto, v_id_almacen, v_id_usuario, 'ENTRADA', 'COMPRA',
     NEW.id_compra, NEW.cantidad, NEW.costo_unitario, v_stock_anterior,
     v_stock_actual, ROUND(v_stock_actual * NEW.costo_unitario, 2),
     'Entrada automática por compra');

    UPDATE productos
       SET precio_compra = NEW.costo_unitario
     WHERE id_producto = NEW.id_producto;
END$$

CREATE TRIGGER trg_detalle_compras_bu_bloquear
BEFORE UPDATE ON detalle_compras
FOR EACH ROW
BEGIN
    SIGNAL SQLSTATE '45000'
        SET MESSAGE_TEXT = 'No se permite editar un detalle de compra; anule y registre nuevamente';
END$$

CREATE TRIGGER trg_detalle_compras_bd_bloquear
BEFORE DELETE ON detalle_compras
FOR EACH ROW
BEGIN
    SIGNAL SQLSTATE '45000'
        SET MESSAGE_TEXT = 'No se permite eliminar un detalle de compra; utilice la anulación';
END$$

CREATE TRIGGER trg_detalle_ventas_bi
BEFORE INSERT ON detalle_ventas
FOR EACH ROW
BEGIN
    DECLARE v_id_almacen INT;
    DECLARE v_estado VARCHAR(20);
    DECLARE v_stock_actual INT DEFAULT 0;
    DECLARE v_costo DECIMAL(12,2) DEFAULT 0.00;

    SELECT id_almacen, estado INTO v_id_almacen, v_estado
      FROM ventas WHERE id_venta = NEW.id_venta;

    IF v_estado IS NULL THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'La venta no existe';
    END IF;
    IF v_estado <> 'EMITIDA' THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'No se puede agregar detalle a una venta anulada';
    END IF;

    SELECT precio_compra INTO v_costo
      FROM productos WHERE id_producto = NEW.id_producto;

    SELECT COALESCE(MAX(stock_actual), 0) INTO v_stock_actual
      FROM stock_almacen
     WHERE id_almacen = v_id_almacen AND id_producto = NEW.id_producto;

    IF v_stock_actual < NEW.cantidad THEN
        SIGNAL SQLSTATE '45000'
            SET MESSAGE_TEXT = 'Stock insuficiente para realizar la venta';
    END IF;

    SET NEW.costo_unitario = v_costo;
    SET NEW.subtotal = ROUND(NEW.cantidad * NEW.precio_unitario, 2);
END$$

CREATE TRIGGER trg_detalle_ventas_ai
AFTER INSERT ON detalle_ventas
FOR EACH ROW
BEGIN
    DECLARE v_id_almacen INT;
    DECLARE v_id_usuario INT;
    DECLARE v_stock_anterior INT DEFAULT 0;
    DECLARE v_stock_actual INT DEFAULT 0;

    SELECT id_almacen, id_usuario
      INTO v_id_almacen, v_id_usuario
      FROM ventas WHERE id_venta = NEW.id_venta;

    SELECT stock_actual INTO v_stock_anterior
      FROM stock_almacen
     WHERE id_almacen = v_id_almacen AND id_producto = NEW.id_producto
     FOR UPDATE;

    IF v_stock_anterior < NEW.cantidad THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Stock insuficiente para realizar la venta';
    END IF;

    UPDATE stock_almacen
       SET stock_actual = stock_actual - NEW.cantidad
     WHERE id_almacen = v_id_almacen AND id_producto = NEW.id_producto;

    SET v_stock_actual = v_stock_anterior - NEW.cantidad;

    INSERT INTO movimientos_stock
    (id_producto, id_almacen, id_usuario, tipo_movimiento, cantidad, motivo,
     referencia_tipo, referencia_id, referencia)
    VALUES
    (NEW.id_producto, v_id_almacen, v_id_usuario, 'SALIDA', NEW.cantidad,
     'Venta registrada', 'VENTA', NEW.id_venta, CONCAT('VENTA-', NEW.id_venta));

    INSERT INTO kardex
    (id_producto, id_almacen, id_usuario, tipo_movimiento, referencia_tipo,
     referencia_id, cantidad, costo_unitario, stock_anterior, stock_actual,
     saldo_valorizado, observacion)
    VALUES
    (NEW.id_producto, v_id_almacen, v_id_usuario, 'SALIDA', 'VENTA',
     NEW.id_venta, NEW.cantidad, NEW.costo_unitario, v_stock_anterior,
     v_stock_actual, ROUND(v_stock_actual * NEW.costo_unitario, 2),
     'Salida automática por venta');
END$$

CREATE TRIGGER trg_detalle_ventas_bu_bloquear
BEFORE UPDATE ON detalle_ventas
FOR EACH ROW
BEGIN
    SIGNAL SQLSTATE '45000'
        SET MESSAGE_TEXT = 'No se permite editar un detalle de venta; anule y registre nuevamente';
END$$

CREATE TRIGGER trg_detalle_ventas_bd_bloquear
BEFORE DELETE ON detalle_ventas
FOR EACH ROW
BEGIN
    SIGNAL SQLSTATE '45000'
        SET MESSAGE_TEXT = 'No se permite eliminar un detalle de venta; utilice la anulación';
END$$

CREATE TRIGGER trg_pagos_bi_validar
BEFORE INSERT ON pagos
FOR EACH ROW
BEGIN
    DECLARE v_total DECIMAL(12,2);
    DECLARE v_pagado DECIMAL(12,2);
    DECLARE v_estado_venta VARCHAR(20);
    DECLARE v_requiere_ref TINYINT;

    SELECT total, estado INTO v_total, v_estado_venta
      FROM ventas WHERE id_venta = NEW.id_venta;

    IF v_estado_venta IS NULL OR v_estado_venta <> 'EMITIDA' THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'No se puede pagar una venta inexistente o anulada';
    END IF;

    SELECT requiere_referencia INTO v_requiere_ref
      FROM metodos_pago WHERE id_metodo_pago = NEW.id_metodo_pago;

    IF v_requiere_ref = 1 AND (NEW.referencia IS NULL OR TRIM(NEW.referencia) = '') THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'El método de pago requiere una referencia';
    END IF;

    SELECT COALESCE(SUM(monto), 0) INTO v_pagado
      FROM pagos WHERE id_venta = NEW.id_venta AND estado = 'REGISTRADO';

    IF v_pagado + NEW.monto > v_total + 0.009 THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'El pago excede el saldo pendiente de la venta';
    END IF;
END$$

CREATE TRIGGER trg_pagos_ai_estado
AFTER INSERT ON pagos
FOR EACH ROW
BEGIN
    DECLARE v_total DECIMAL(12,2);
    DECLARE v_pagado DECIMAL(12,2);

    SELECT total INTO v_total FROM ventas WHERE id_venta = NEW.id_venta;
    SELECT COALESCE(SUM(monto), 0) INTO v_pagado
      FROM pagos WHERE id_venta = NEW.id_venta AND estado = 'REGISTRADO';

    UPDATE ventas
       SET estado_pago = CASE
           WHEN v_pagado <= 0 THEN 'PENDIENTE'
           WHEN v_pagado + 0.009 >= v_total THEN 'PAGADO'
           ELSE 'PARCIAL'
       END
     WHERE id_venta = NEW.id_venta;
END$$

CREATE TRIGGER trg_pagos_au_estado
AFTER UPDATE ON pagos
FOR EACH ROW
BEGIN
    DECLARE v_total DECIMAL(12,2);
    DECLARE v_pagado DECIMAL(12,2);

    SELECT total INTO v_total FROM ventas WHERE id_venta = NEW.id_venta;
    SELECT COALESCE(SUM(monto), 0) INTO v_pagado
      FROM pagos WHERE id_venta = NEW.id_venta AND estado = 'REGISTRADO';

    UPDATE ventas
       SET estado_pago = CASE
           WHEN v_pagado <= 0 THEN 'PENDIENTE'
           WHEN v_pagado + 0.009 >= v_total THEN 'PAGADO'
           ELSE 'PARCIAL'
       END
     WHERE id_venta = NEW.id_venta;
END$$

-- =====================================================================
-- PROCEDIMIENTOS CONTROLADOS
-- =====================================================================
CREATE PROCEDURE sp_anular_venta(
    IN p_id_venta BIGINT,
    IN p_id_usuario INT,
    IN p_motivo VARCHAR(200)
)
BEGIN
    DECLARE v_estado VARCHAR(20);
    DECLARE v_id_almacen INT;
    DECLARE v_producto INT;
    DECLARE v_cantidad INT;
    DECLARE v_costo DECIMAL(12,2);
    DECLARE v_stock_anterior INT;
    DECLARE v_stock_actual INT;
    DECLARE done INT DEFAULT 0;

    DECLARE cur CURSOR FOR
        SELECT id_producto, cantidad, costo_unitario
          FROM detalle_ventas WHERE id_venta = p_id_venta;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
    DECLARE EXIT HANDLER FOR SQLEXCEPTION
    BEGIN
        ROLLBACK;
        RESIGNAL;
    END;

    START TRANSACTION;

    SELECT estado, id_almacen INTO v_estado, v_id_almacen
      FROM ventas WHERE id_venta = p_id_venta FOR UPDATE;

    IF v_estado IS NULL THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'La venta no existe';
    END IF;
    IF v_estado = 'ANULADA' THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'La venta ya está anulada';
    END IF;
    IF EXISTS (SELECT 1 FROM pagos WHERE id_venta = p_id_venta AND estado = 'REGISTRADO') THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Anule primero los pagos registrados de la venta';
    END IF;

    OPEN cur;
    read_loop: LOOP
        FETCH cur INTO v_producto, v_cantidad, v_costo;
        IF done = 1 THEN LEAVE read_loop; END IF;

        SELECT stock_actual INTO v_stock_anterior
          FROM stock_almacen
         WHERE id_almacen = v_id_almacen AND id_producto = v_producto
         FOR UPDATE;

        UPDATE stock_almacen
           SET stock_actual = stock_actual + v_cantidad
         WHERE id_almacen = v_id_almacen AND id_producto = v_producto;

        SET v_stock_actual = v_stock_anterior + v_cantidad;

        INSERT INTO movimientos_stock
        (id_producto, id_almacen, id_usuario, tipo_movimiento, cantidad, motivo,
         referencia_tipo, referencia_id, referencia)
        VALUES
        (v_producto, v_id_almacen, p_id_usuario, 'ENTRADA', v_cantidad,
         'Anulación de venta', 'ANULACION', p_id_venta,
         CONCAT('ANULA-VENTA-', p_id_venta));

        INSERT INTO kardex
        (id_producto, id_almacen, id_usuario, tipo_movimiento, referencia_tipo,
         referencia_id, cantidad, costo_unitario, stock_anterior, stock_actual,
         saldo_valorizado, observacion)
        VALUES
        (v_producto, v_id_almacen, p_id_usuario, 'ENTRADA', 'ANULACION',
         p_id_venta, v_cantidad, v_costo, v_stock_anterior, v_stock_actual,
         ROUND(v_stock_actual * v_costo, 2), 'Reversión por anulación de venta');
    END LOOP;
    CLOSE cur;

    UPDATE ventas
       SET estado = 'ANULADA', estado_pago = 'PENDIENTE',
           fecha_anulacion = NOW(), motivo_anulacion = p_motivo
     WHERE id_venta = p_id_venta;

    INSERT INTO auditoria
    (id_usuario, tabla_afectada, registro_id, accion, descripcion)
    VALUES
    (p_id_usuario, 'ventas', p_id_venta, 'ANULAR', p_motivo);

    COMMIT;
END$$

CREATE PROCEDURE sp_anular_compra(
    IN p_id_compra BIGINT,
    IN p_id_usuario INT,
    IN p_motivo VARCHAR(200)
)
BEGIN
    DECLARE v_estado VARCHAR(20);
    DECLARE v_id_almacen INT;
    DECLARE v_producto INT;
    DECLARE v_cantidad INT;
    DECLARE v_costo DECIMAL(12,2);
    DECLARE v_stock_anterior INT;
    DECLARE v_stock_actual INT;
    DECLARE done INT DEFAULT 0;

    DECLARE cur CURSOR FOR
        SELECT id_producto, cantidad, costo_unitario
          FROM detalle_compras WHERE id_compra = p_id_compra;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
    DECLARE EXIT HANDLER FOR SQLEXCEPTION
    BEGIN
        ROLLBACK;
        RESIGNAL;
    END;

    START TRANSACTION;

    SELECT estado, id_almacen INTO v_estado, v_id_almacen
      FROM compras WHERE id_compra = p_id_compra FOR UPDATE;

    IF v_estado IS NULL THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'La compra no existe';
    END IF;
    IF v_estado = 'ANULADA' THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'La compra ya está anulada';
    END IF;

    OPEN cur;
    read_loop: LOOP
        FETCH cur INTO v_producto, v_cantidad, v_costo;
        IF done = 1 THEN LEAVE read_loop; END IF;

        SELECT stock_actual INTO v_stock_anterior
          FROM stock_almacen
         WHERE id_almacen = v_id_almacen AND id_producto = v_producto
         FOR UPDATE;

        IF v_stock_anterior < v_cantidad THEN
            SIGNAL SQLSTATE '45000'
                SET MESSAGE_TEXT = 'No se puede anular la compra: parte del stock ya fue utilizado';
        END IF;

        UPDATE stock_almacen
           SET stock_actual = stock_actual - v_cantidad
         WHERE id_almacen = v_id_almacen AND id_producto = v_producto;

        SET v_stock_actual = v_stock_anterior - v_cantidad;

        INSERT INTO movimientos_stock
        (id_producto, id_almacen, id_usuario, tipo_movimiento, cantidad, motivo,
         referencia_tipo, referencia_id, referencia)
        VALUES
        (v_producto, v_id_almacen, p_id_usuario, 'SALIDA', v_cantidad,
         'Anulación de compra', 'ANULACION', p_id_compra,
         CONCAT('ANULA-COMPRA-', p_id_compra));

        INSERT INTO kardex
        (id_producto, id_almacen, id_usuario, tipo_movimiento, referencia_tipo,
         referencia_id, cantidad, costo_unitario, stock_anterior, stock_actual,
         saldo_valorizado, observacion)
        VALUES
        (v_producto, v_id_almacen, p_id_usuario, 'SALIDA', 'ANULACION',
         p_id_compra, v_cantidad, v_costo, v_stock_anterior, v_stock_actual,
         ROUND(v_stock_actual * v_costo, 2), 'Reversión por anulación de compra');
    END LOOP;
    CLOSE cur;

    UPDATE compras
       SET estado = 'ANULADA', fecha_anulacion = NOW(), motivo_anulacion = p_motivo
     WHERE id_compra = p_id_compra;

    INSERT INTO auditoria
    (id_usuario, tabla_afectada, registro_id, accion, descripcion)
    VALUES
    (p_id_usuario, 'compras', p_id_compra, 'ANULAR', p_motivo);

    COMMIT;
END$$

CREATE PROCEDURE sp_trasladar_stock(
    IN p_id_producto INT,
    IN p_id_almacen_origen INT,
    IN p_id_almacen_destino INT,
    IN p_cantidad INT,
    IN p_id_usuario INT,
    IN p_observacion VARCHAR(200)
)
BEGIN
    DECLARE v_stock_origen INT;
    DECLARE v_stock_destino INT;
    DECLARE v_costo DECIMAL(12,2);
    DECLARE v_ref BIGINT;
    DECLARE EXIT HANDLER FOR SQLEXCEPTION
    BEGIN
        ROLLBACK;
        RESIGNAL;
    END;

    IF p_id_almacen_origen = p_id_almacen_destino THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Los almacenes de origen y destino deben ser diferentes';
    END IF;
    IF p_cantidad <= 0 THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'La cantidad debe ser mayor que cero';
    END IF;

    START TRANSACTION;

    SELECT precio_compra INTO v_costo FROM productos WHERE id_producto = p_id_producto;

    SELECT stock_actual INTO v_stock_origen
      FROM stock_almacen
     WHERE id_almacen = p_id_almacen_origen AND id_producto = p_id_producto
     FOR UPDATE;

    IF v_stock_origen < p_cantidad THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Stock insuficiente en el almacén de origen';
    END IF;

    INSERT INTO stock_almacen (id_almacen, id_producto, stock_actual)
    VALUES (p_id_almacen_destino, p_id_producto, 0)
    ON DUPLICATE KEY UPDATE stock_actual = stock_actual;

    SELECT stock_actual INTO v_stock_destino
      FROM stock_almacen
     WHERE id_almacen = p_id_almacen_destino AND id_producto = p_id_producto
     FOR UPDATE;

    SET v_ref = UNIX_TIMESTAMP(NOW(6));

    UPDATE stock_almacen SET stock_actual = stock_actual - p_cantidad
     WHERE id_almacen = p_id_almacen_origen AND id_producto = p_id_producto;
    UPDATE stock_almacen SET stock_actual = stock_actual + p_cantidad
     WHERE id_almacen = p_id_almacen_destino AND id_producto = p_id_producto;

    INSERT INTO movimientos_stock
    (id_producto, id_almacen, id_almacen_destino, id_usuario, tipo_movimiento,
     cantidad, motivo, referencia_tipo, referencia_id, referencia)
    VALUES
    (p_id_producto, p_id_almacen_origen, p_id_almacen_destino, p_id_usuario,
     'TRASLADO', p_cantidad, COALESCE(p_observacion,'Traslado entre almacenes'),
     'TRASLADO', v_ref, CONCAT('TRASLADO-', v_ref));

    INSERT INTO kardex
    (id_producto, id_almacen, id_usuario, tipo_movimiento, referencia_tipo,
     referencia_id, cantidad, costo_unitario, stock_anterior, stock_actual,
     saldo_valorizado, observacion)
    VALUES
    (p_id_producto, p_id_almacen_origen, p_id_usuario, 'TRASLADO', 'TRASLADO',
     v_ref, p_cantidad, v_costo, v_stock_origen, v_stock_origen - p_cantidad,
     ROUND((v_stock_origen - p_cantidad) * v_costo,2), 'Salida por traslado'),
    (p_id_producto, p_id_almacen_destino, p_id_usuario, 'TRASLADO', 'TRASLADO',
     v_ref, p_cantidad, v_costo, v_stock_destino, v_stock_destino + p_cantidad,
     ROUND((v_stock_destino + p_cantidad) * v_costo,2), 'Entrada por traslado');

    COMMIT;
END$$

DELIMITER ;

-- =====================================================================
-- VISTAS DE CONSULTA
-- =====================================================================
CREATE OR REPLACE VIEW vw_usuarios_roles AS
SELECT
    u.id_usuario,
    u.nombre AS usuario,
    u.email,
    u.estado AS estado_usuario,
    GROUP_CONCAT(r.nombre ORDER BY r.nombre SEPARATOR ', ') AS roles
FROM usuarios u
LEFT JOIN usuario_roles ur ON ur.id_usuario = u.id_usuario
LEFT JOIN roles r ON r.id_rol = ur.id_rol
GROUP BY u.id_usuario, u.nombre, u.email, u.estado;

CREATE OR REPLACE VIEW vw_stock_actual AS
SELECT
    sa.id_almacen,
    a.nombre AS almacen,
    p.id_producto,
    p.codigo,
    p.nombre AS producto,
    c.nombre AS categoria,
    m.nombre AS marca,
    sa.stock_actual,
    p.stock_minimo,
    CASE WHEN sa.stock_actual <= p.stock_minimo THEN 'STOCK BAJO' ELSE 'STOCK NORMAL' END AS estado_stock,
    p.precio_compra,
    ROUND(sa.stock_actual * p.precio_compra, 2) AS valor_stock
FROM stock_almacen sa
INNER JOIN almacenes a ON a.id_almacen = sa.id_almacen
INNER JOIN productos p ON p.id_producto = sa.id_producto
INNER JOIN categorias c ON c.id_categoria = p.id_categoria
INNER JOIN marcas m ON m.id_marca = p.id_marca;

CREATE OR REPLACE VIEW vw_ventas_detalladas AS
SELECT
    v.id_venta,
    CONCAT(v.serie, '-', v.numero) AS comprobante,
    v.fecha,
    c.nombre AS cliente,
    u.nombre AS vendedor,
    a.nombre AS almacen,
    p.codigo,
    p.nombre AS producto,
    dv.cantidad,
    dv.costo_unitario,
    dv.precio_unitario,
    dv.subtotal,
    ROUND((dv.precio_unitario - dv.costo_unitario) * dv.cantidad, 2) AS utilidad_bruta,
    v.igv,
    v.total,
    v.estado,
    v.estado_pago
FROM ventas v
INNER JOIN clientes c ON c.id_cliente = v.id_cliente
INNER JOIN usuarios u ON u.id_usuario = v.id_usuario
INNER JOIN almacenes a ON a.id_almacen = v.id_almacen
INNER JOIN detalle_ventas dv ON dv.id_venta = v.id_venta
INNER JOIN productos p ON p.id_producto = dv.id_producto;

CREATE OR REPLACE VIEW vw_saldos_ventas AS
SELECT
    v.id_venta,
    CONCAT(v.serie, '-', v.numero) AS comprobante,
    v.fecha,
    c.nombre AS cliente,
    v.total,
    COALESCE(SUM(CASE WHEN p.estado = 'REGISTRADO' THEN p.monto ELSE 0 END), 0) AS total_pagado,
    ROUND(v.total - COALESCE(SUM(CASE WHEN p.estado = 'REGISTRADO' THEN p.monto ELSE 0 END), 0), 2) AS saldo_pendiente,
    v.estado_pago,
    v.estado
FROM ventas v
INNER JOIN clientes c ON c.id_cliente = v.id_cliente
LEFT JOIN pagos p ON p.id_venta = v.id_venta
GROUP BY v.id_venta, v.serie, v.numero, v.fecha, c.nombre, v.total, v.estado_pago, v.estado;

-- =====================================================================
-- DATOS SEMILLA
-- DATOS SEMILLA COMPLETOS
-- Estos registros se ejecutan automáticamente después de crear la estructura.
-- Usuario demo: admin@sistema.com | contraseña: password
-- =====================================================================
START TRANSACTION;

INSERT INTO empresa
(ruc, razon_social, nombre_comercial, direccion, telefono, email)
VALUES
('20600000099','Sistema Ventas Demo SAC','Ventas Demo',
 'Av. Principal 123 - Lima','999888777','contacto@ventasdemo.com');

INSERT INTO roles (nombre, descripcion, es_sistema) VALUES
('ADMINISTRADOR','Acceso total al sistema',1),
('VENDEDOR','Gestiona clientes, ventas y pagos',1),
('ALMACENERO','Gestiona productos, compras e inventario',1),
('CAJERO','Gestiona caja y pagos',1),
('SUPERVISOR','Supervisa operaciones y reportes',0),
('CONSULTA','Acceso de solo lectura',0);

INSERT INTO usuarios (nombre,email,imagen,password_hash) VALUES
('Administrador General','admin@sistema.com','uploads/usuarios/admin.png','$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi'),
('Vendedor Principal','vendedor@sistema.com','uploads/usuarios/vendedor.png','$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi'),
('Almacenero Principal','almacen@sistema.com','uploads/usuarios/almacenero.png','$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi'),
('Cajero Principal','caja@sistema.com','uploads/usuarios/cajero.png','$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi'),
('Supervisor Operativo','supervisor@sistema.com','uploads/usuarios/supervisor.png','$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi'),
('Usuario Consulta','consulta@sistema.com','uploads/usuarios/consulta.png','$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi'),
('Vendedor Secundario','vendedor2@sistema.com','uploads/usuarios/usuario_default.png','$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi'),
('Almacenero Secundario','almacen2@sistema.com','uploads/usuarios/usuario_default.png','$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi');
INSERT INTO usuario_roles (id_usuario,id_rol) VALUES
(1,1),(2,2),(3,3),(4,4),(5,5),(6,6),(7,2),(8,3);

INSERT INTO modulos (nombre,ruta,icono,orden_menu) VALUES
('Empresa','/empresa','bi-building',1),
('Usuarios','/usuarios','bi-people',2),
('Roles','/roles','bi-shield-lock',3),
('Clientes','/clientes','bi-person-vcard',4),
('Proveedores','/proveedores','bi-truck',5),
('Categorías','/categorias','bi-tags',6),
('Marcas','/marcas','bi-bookmark',7),
('Productos','/productos','bi-box-seam',8),
('Almacenes','/almacenes','bi-buildings',9),
('Inventario','/inventario','bi-boxes',10),
('Kardex','/kardex','bi-journal-text',11),
('Compras','/compras','bi-cart-plus',12),
('Ventas','/ventas','bi-cart-check',13),
('Pagos','/pagos','bi-cash-coin',14),
('Caja','/caja','bi-safe',15),
('Reportes','/reportes','bi-bar-chart',16),
('Auditoría','/auditoria','bi-clipboard-data',17);

INSERT INTO permisos (id_modulo,nombre,descripcion)
SELECT id_modulo,'ver',CONCAT('Ver ',nombre) FROM modulos;
INSERT INTO permisos (id_modulo,nombre,descripcion)
SELECT id_modulo,'crear',CONCAT('Crear ',nombre) FROM modulos
WHERE nombre NOT IN ('Reportes','Auditoría','Kardex');
INSERT INTO permisos (id_modulo,nombre,descripcion)
SELECT id_modulo,'editar',CONCAT('Editar ',nombre) FROM modulos
WHERE nombre NOT IN ('Reportes','Auditoría','Kardex');
INSERT INTO permisos (id_modulo,nombre,descripcion)
SELECT id_modulo,'anular',CONCAT('Anular ',nombre) FROM modulos
WHERE nombre IN ('Compras','Ventas','Pagos');
INSERT INTO permisos (id_modulo,nombre,descripcion)
SELECT id_modulo,'eliminar',CONCAT('Eliminar ',nombre) FROM modulos
WHERE nombre IN ('Clientes','Proveedores','Categorías','Marcas','Productos','Usuarios','Roles');

INSERT INTO rol_permisos (id_rol,id_permiso)
SELECT 1,id_permiso FROM permisos;
INSERT INTO rol_permisos (id_rol,id_permiso)
SELECT 2,p.id_permiso FROM permisos p JOIN modulos m ON m.id_modulo=p.id_modulo
WHERE m.nombre IN ('Clientes','Productos','Ventas','Pagos','Reportes');
INSERT INTO rol_permisos (id_rol,id_permiso)
SELECT 3,p.id_permiso FROM permisos p JOIN modulos m ON m.id_modulo=p.id_modulo
WHERE m.nombre IN ('Proveedores','Categorías','Marcas','Productos','Almacenes','Inventario','Kardex','Compras','Reportes');
INSERT INTO rol_permisos (id_rol,id_permiso)
SELECT 4,p.id_permiso FROM permisos p JOIN modulos m ON m.id_modulo=p.id_modulo
WHERE m.nombre IN ('Pagos','Caja','Reportes');
INSERT INTO rol_permisos (id_rol,id_permiso)
SELECT 5,p.id_permiso FROM permisos p JOIN modulos m ON m.id_modulo=p.id_modulo
WHERE m.nombre NOT IN ('Roles');
INSERT INTO rol_permisos (id_rol,id_permiso)
SELECT 6,p.id_permiso FROM permisos p WHERE p.nombre='ver';

INSERT INTO clientes (tipo_documento,numero_documento,nombre,direccion,telefono,email) VALUES
('DNI','12345678','Juan Pérez','Av. Principal 123','999111222','juan.perez@gmail.com'),
('RUC','20123456789','Empresa Demo SAC','Av. Comercial 456','987654321','ventas@empresademo.com'),
('CE','CE12345678','Carlos Ramírez','Jr. Las Flores 321','988111333','carlos.ramirez@gmail.com'),
('DNI','45678912','María Torres','Av. Los Olivos 225','987111444','maria.torres@gmail.com'),
('DNI','78912345','Luis Mendoza','Jr. Amazonas 180','986222555','luis.mendoza@gmail.com'),
('RUC','20567890123','Servicios Integrales EIRL','Av. Industrial 890','985333666','compras@serviciosintegrales.pe'),
('DNI','32165498','Ana Vargas','Calle Las Palmeras 450','984444777','ana.vargas@gmail.com'),
('CE','CE87654321','Diego Fernández','Av. Canadá 1500','983555888','diego.fernandez@gmail.com'),
('RUC','20612345678','Comercial Andina SAC','Av. Javier Prado 2200','982666999','administracion@comercialandina.pe'),
('DNI','65498732','Rosa Castillo','Jr. Junín 640','981777000','rosa.castillo@gmail.com');

INSERT INTO proveedores (ruc,razon_social,telefono,email,direccion) VALUES
('20600000001','Proveedor Tecnológico SAC','999888777','ventas@proveedortecnologico.pe','Av. Industrial 100'),
('20600000002','Distribuidora Digital EIRL','988777666','pedidos@digital.com.pe','Jr. Comercio 200'),
('20600000003','Importaciones del Pacífico SAC','977666555','ventas@importpacifico.pe','Av. Argentina 850'),
('20600000004','Soluciones Informáticas EIRL','966555444','contacto@solucionesit.pe','Av. Arequipa 1200'),
('20600000005','Mayorista de Cómputo SAC','955444333','pedidos@mayoristacomputo.pe','Av. Colonial 1500'),
('20600000006','Redes y Conectividad SAC','944333222','ventas@redesyconectividad.pe','Jr. Paruro 450'),
('20600000007','Energía Segura EIRL','933222111','pedidos@energiasegura.pe','Av. México 980'),
('20600000008','Suministros Empresariales SAC','922111000','ventas@suministrosempresariales.pe','Av. Universitaria 3100');

INSERT INTO categorias (nombre,descripcion) VALUES
('Computadoras','Laptops y computadoras de escritorio'),
('Periféricos','Mouse, teclados, cámaras y audífonos'),
('Componentes','Memorias, discos y componentes internos'),
('Accesorios','Cables, adaptadores y accesorios'),
('Impresión','Impresoras, tintas y suministros'),
('Redes','Routers, switches y conectividad'),
('Energía','UPS, estabilizadores y protección eléctrica'),
('Almacenamiento','Discos externos y memorias portátiles');

INSERT INTO marcas (nombre,descripcion) VALUES
('HP','Equipos de cómputo e impresión'),
('Lenovo','Computadoras y laptops'),
('Logitech','Periféricos'),
('Kingston','Memorias y almacenamiento'),
('Epson','Impresión'),
('TP-Link','Redes y conectividad'),
('APC','Protección eléctrica'),
('Western Digital','Almacenamiento'),
('Samsung','Monitores y almacenamiento'),
('Intel','Procesadores y componentes');

INSERT INTO productos (id_categoria,id_marca,codigo,nombre,descripcion,imagen,precio_compra,precio_venta,stock_minimo) VALUES
(1,1,'PROD001','Laptop HP Core i5','Laptop para oficina y estudio','uploads/productos/prod001.png',2000,2500,2),
(2,3,'PROD002','Mouse Logitech M185','Mouse inalámbrico','uploads/productos/prod002.png',40,65,5),
(2,3,'PROD003','Teclado Logitech K120','Teclado USB','uploads/productos/prod003.png',55,85,5),
(3,4,'PROD004','SSD Kingston 1TB','Disco sólido SSD 1TB','uploads/productos/prod004.png',250,350,3),
(2,3,'PROD005','Webcam Logitech C920','Cámara Full HD','uploads/productos/prod005.png',320,480,2),
(2,3,'PROD006','Audífonos Logitech H390','Audífonos USB con micrófono','uploads/productos/prod006.png',90,145,4),
(4,1,'PROD007','Mochila HP 15.6','Mochila para laptop','uploads/productos/prod007.png',100,180,4),
(1,2,'PROD008','Laptop Lenovo Ryzen 5','Laptop de productividad','uploads/productos/prod008.png',500,750,2),
(3,10,'PROD009','Procesador Intel Core i5','Procesador para escritorio','uploads/productos/prod009.png',55,95,3),
(4,3,'PROD010','Hub USB 3.0','Hub de cuatro puertos','uploads/productos/prod010.png',70,120,5),
(4,6,'PROD011','Cable de red Cat6','Cable de red de 5 metros','uploads/productos/prod011.png',50,95,8),
(5,5,'PROD012','Impresora Epson L3250','Impresora multifuncional','uploads/productos/prod012.png',650,850,2),
(5,5,'PROD013','Tinta Epson Negra','Botella de tinta negra','uploads/productos/prod013.png',35,55,10),
(6,6,'PROD014','Router TP-Link Archer C6','Router inalámbrico dual band','uploads/productos/prod014.png',120,190,3),
(6,6,'PROD015','Switch TP-Link 8 puertos','Switch gigabit','uploads/productos/prod015.png',95,150,3),
(7,7,'PROD016','UPS APC 650VA','Respaldo de energía','uploads/productos/prod016.png',250,390,2),
(7,7,'PROD017','Estabilizador APC 1200VA','Protección eléctrica','uploads/productos/prod017.png',110,175,3),
(8,8,'PROD018','Disco Externo WD 2TB','Almacenamiento portátil','uploads/productos/prod018.png',280,410,3),
(8,9,'PROD019','Memoria USB Samsung 128GB','Memoria portátil','uploads/productos/prod019.png',45,75,6),
(3,4,'PROD020','Memoria RAM Kingston 16GB','Memoria DDR4','uploads/productos/prod020.png',130,210,4);

INSERT INTO tipos_comprobante (codigo,nombre,tipo) VALUES
('BV','BOLETA DE VENTA','VENTA'),
('FV','FACTURA DE VENTA','VENTA'),
('TC','TICKET DE VENTA','VENTA'),
('FC','FACTURA DE COMPRA','COMPRA'),
('RC','RECIBO DE COMPRA','COMPRA');

INSERT INTO series_comprobantes (id_tipo_comprobante,serie,numero_actual) VALUES
(1,'B001',4),(1,'B002',0),(2,'F001',0),(3,'T001',0),(4,'C001',3),(5,'R001',0);

INSERT INTO metodos_pago (nombre,descripcion,requiere_referencia) VALUES
('EFECTIVO','Pago en efectivo',0),
('YAPE','Pago mediante Yape',1),
('PLIN','Pago mediante Plin',1),
('TRANSFERENCIA','Transferencia bancaria',1),
('TARJETA VISA','Pago con tarjeta Visa',1),
('TARJETA MASTERCARD','Pago con tarjeta Mastercard',1),
('CRÉDITO','Venta al crédito',0);

INSERT INTO almacenes (nombre,direccion) VALUES
('Almacén Principal','Av. Principal 123 - Lima'),
('Tienda Central','Av. Comercial 456 - Lima'),
('Depósito Auxiliar','Av. Industrial 789 - Lima');

INSERT INTO stock_almacen (id_almacen,id_producto,stock_actual) VALUES
(1,1,28),
(1,2,31),
(1,3,34),
(1,4,37),
(1,5,40),
(1,6,43),
(1,7,20),
(1,8,23),
(1,9,26),
(1,10,29),
(1,11,32),
(1,12,35),
(1,13,38),
(1,14,41),
(1,15,44),
(1,16,21),
(1,17,24),
(1,18,27),
(1,19,30),
(1,20,33),
(2,1,33),
(2,2,36),
(2,3,39),
(2,4,42),
(2,5,45),
(2,6,22),
(2,7,25),
(2,8,28),
(2,9,31),
(2,10,34),
(2,11,37),
(2,12,40),
(2,13,43),
(2,14,20),
(2,15,23),
(2,16,26),
(2,17,29),
(2,18,32),
(2,19,35),
(2,20,38),
(3,1,38),
(3,2,41),
(3,3,44),
(3,4,21),
(3,5,24),
(3,6,27),
(3,7,30),
(3,8,33),
(3,9,36),
(3,10,39),
(3,11,42),
(3,12,45),
(3,13,22),
(3,14,25),
(3,15,28),
(3,16,31),
(3,17,34),
(3,18,37),
(3,19,40),
(3,20,43);

INSERT INTO movimientos_stock (id_producto,id_almacen,id_usuario,tipo_movimiento,cantidad,motivo,referencia_tipo,referencia) VALUES
(1,1,3,'AJUSTE',28,'Carga inicial','INICIAL','INICIAL'),
(2,1,3,'AJUSTE',31,'Carga inicial','INICIAL','INICIAL'),
(3,1,3,'AJUSTE',34,'Carga inicial','INICIAL','INICIAL'),
(4,1,3,'AJUSTE',37,'Carga inicial','INICIAL','INICIAL'),
(5,1,3,'AJUSTE',40,'Carga inicial','INICIAL','INICIAL'),
(6,1,3,'AJUSTE',43,'Carga inicial','INICIAL','INICIAL'),
(7,1,3,'AJUSTE',20,'Carga inicial','INICIAL','INICIAL'),
(8,1,3,'AJUSTE',23,'Carga inicial','INICIAL','INICIAL'),
(9,1,3,'AJUSTE',26,'Carga inicial','INICIAL','INICIAL'),
(10,1,3,'AJUSTE',29,'Carga inicial','INICIAL','INICIAL'),
(11,1,3,'AJUSTE',32,'Carga inicial','INICIAL','INICIAL'),
(12,1,3,'AJUSTE',35,'Carga inicial','INICIAL','INICIAL'),
(13,1,3,'AJUSTE',38,'Carga inicial','INICIAL','INICIAL'),
(14,1,3,'AJUSTE',41,'Carga inicial','INICIAL','INICIAL'),
(15,1,3,'AJUSTE',44,'Carga inicial','INICIAL','INICIAL'),
(16,1,3,'AJUSTE',21,'Carga inicial','INICIAL','INICIAL'),
(17,1,3,'AJUSTE',24,'Carga inicial','INICIAL','INICIAL'),
(18,1,3,'AJUSTE',27,'Carga inicial','INICIAL','INICIAL'),
(19,1,3,'AJUSTE',30,'Carga inicial','INICIAL','INICIAL'),
(20,1,3,'AJUSTE',33,'Carga inicial','INICIAL','INICIAL'),
(1,2,3,'AJUSTE',33,'Carga inicial','INICIAL','INICIAL'),
(2,2,3,'AJUSTE',36,'Carga inicial','INICIAL','INICIAL'),
(3,2,3,'AJUSTE',39,'Carga inicial','INICIAL','INICIAL'),
(4,2,3,'AJUSTE',42,'Carga inicial','INICIAL','INICIAL'),
(5,2,3,'AJUSTE',45,'Carga inicial','INICIAL','INICIAL'),
(6,2,3,'AJUSTE',22,'Carga inicial','INICIAL','INICIAL'),
(7,2,3,'AJUSTE',25,'Carga inicial','INICIAL','INICIAL'),
(8,2,3,'AJUSTE',28,'Carga inicial','INICIAL','INICIAL'),
(9,2,3,'AJUSTE',31,'Carga inicial','INICIAL','INICIAL'),
(10,2,3,'AJUSTE',34,'Carga inicial','INICIAL','INICIAL'),
(11,2,3,'AJUSTE',37,'Carga inicial','INICIAL','INICIAL'),
(12,2,3,'AJUSTE',40,'Carga inicial','INICIAL','INICIAL'),
(13,2,3,'AJUSTE',43,'Carga inicial','INICIAL','INICIAL'),
(14,2,3,'AJUSTE',20,'Carga inicial','INICIAL','INICIAL'),
(15,2,3,'AJUSTE',23,'Carga inicial','INICIAL','INICIAL'),
(16,2,3,'AJUSTE',26,'Carga inicial','INICIAL','INICIAL'),
(17,2,3,'AJUSTE',29,'Carga inicial','INICIAL','INICIAL'),
(18,2,3,'AJUSTE',32,'Carga inicial','INICIAL','INICIAL'),
(19,2,3,'AJUSTE',35,'Carga inicial','INICIAL','INICIAL'),
(20,2,3,'AJUSTE',38,'Carga inicial','INICIAL','INICIAL'),
(1,3,3,'AJUSTE',38,'Carga inicial','INICIAL','INICIAL'),
(2,3,3,'AJUSTE',41,'Carga inicial','INICIAL','INICIAL'),
(3,3,3,'AJUSTE',44,'Carga inicial','INICIAL','INICIAL'),
(4,3,3,'AJUSTE',21,'Carga inicial','INICIAL','INICIAL'),
(5,3,3,'AJUSTE',24,'Carga inicial','INICIAL','INICIAL'),
(6,3,3,'AJUSTE',27,'Carga inicial','INICIAL','INICIAL'),
(7,3,3,'AJUSTE',30,'Carga inicial','INICIAL','INICIAL'),
(8,3,3,'AJUSTE',33,'Carga inicial','INICIAL','INICIAL'),
(9,3,3,'AJUSTE',36,'Carga inicial','INICIAL','INICIAL'),
(10,3,3,'AJUSTE',39,'Carga inicial','INICIAL','INICIAL'),
(11,3,3,'AJUSTE',42,'Carga inicial','INICIAL','INICIAL'),
(12,3,3,'AJUSTE',45,'Carga inicial','INICIAL','INICIAL'),
(13,3,3,'AJUSTE',22,'Carga inicial','INICIAL','INICIAL'),
(14,3,3,'AJUSTE',25,'Carga inicial','INICIAL','INICIAL'),
(15,3,3,'AJUSTE',28,'Carga inicial','INICIAL','INICIAL'),
(16,3,3,'AJUSTE',31,'Carga inicial','INICIAL','INICIAL'),
(17,3,3,'AJUSTE',34,'Carga inicial','INICIAL','INICIAL'),
(18,3,3,'AJUSTE',37,'Carga inicial','INICIAL','INICIAL'),
(19,3,3,'AJUSTE',40,'Carga inicial','INICIAL','INICIAL'),
(20,3,3,'AJUSTE',43,'Carga inicial','INICIAL','INICIAL');

INSERT INTO kardex (id_producto,id_almacen,id_usuario,tipo_movimiento,referencia_tipo,cantidad,costo_unitario,stock_anterior,stock_actual,saldo_valorizado,observacion) VALUES
(1,1,3,'AJUSTE','INICIAL',28,2000,0,28,56000,'Stock inicial de datos semilla'),
(2,1,3,'AJUSTE','INICIAL',31,40,0,31,1240,'Stock inicial de datos semilla'),
(3,1,3,'AJUSTE','INICIAL',34,55,0,34,1870,'Stock inicial de datos semilla'),
(4,1,3,'AJUSTE','INICIAL',37,250,0,37,9250,'Stock inicial de datos semilla'),
(5,1,3,'AJUSTE','INICIAL',40,320,0,40,12800,'Stock inicial de datos semilla'),
(6,1,3,'AJUSTE','INICIAL',43,90,0,43,3870,'Stock inicial de datos semilla'),
(7,1,3,'AJUSTE','INICIAL',20,100,0,20,2000,'Stock inicial de datos semilla'),
(8,1,3,'AJUSTE','INICIAL',23,500,0,23,11500,'Stock inicial de datos semilla'),
(9,1,3,'AJUSTE','INICIAL',26,55,0,26,1430,'Stock inicial de datos semilla'),
(10,1,3,'AJUSTE','INICIAL',29,70,0,29,2030,'Stock inicial de datos semilla'),
(11,1,3,'AJUSTE','INICIAL',32,50,0,32,1600,'Stock inicial de datos semilla'),
(12,1,3,'AJUSTE','INICIAL',35,650,0,35,22750,'Stock inicial de datos semilla'),
(13,1,3,'AJUSTE','INICIAL',38,35,0,38,1330,'Stock inicial de datos semilla'),
(14,1,3,'AJUSTE','INICIAL',41,120,0,41,4920,'Stock inicial de datos semilla'),
(15,1,3,'AJUSTE','INICIAL',44,95,0,44,4180,'Stock inicial de datos semilla'),
(16,1,3,'AJUSTE','INICIAL',21,250,0,21,5250,'Stock inicial de datos semilla'),
(17,1,3,'AJUSTE','INICIAL',24,110,0,24,2640,'Stock inicial de datos semilla'),
(18,1,3,'AJUSTE','INICIAL',27,280,0,27,7560,'Stock inicial de datos semilla'),
(19,1,3,'AJUSTE','INICIAL',30,45,0,30,1350,'Stock inicial de datos semilla'),
(20,1,3,'AJUSTE','INICIAL',33,130,0,33,4290,'Stock inicial de datos semilla'),
(1,2,3,'AJUSTE','INICIAL',33,2000,0,33,66000,'Stock inicial de datos semilla'),
(2,2,3,'AJUSTE','INICIAL',36,40,0,36,1440,'Stock inicial de datos semilla'),
(3,2,3,'AJUSTE','INICIAL',39,55,0,39,2145,'Stock inicial de datos semilla'),
(4,2,3,'AJUSTE','INICIAL',42,250,0,42,10500,'Stock inicial de datos semilla'),
(5,2,3,'AJUSTE','INICIAL',45,320,0,45,14400,'Stock inicial de datos semilla'),
(6,2,3,'AJUSTE','INICIAL',22,90,0,22,1980,'Stock inicial de datos semilla'),
(7,2,3,'AJUSTE','INICIAL',25,100,0,25,2500,'Stock inicial de datos semilla'),
(8,2,3,'AJUSTE','INICIAL',28,500,0,28,14000,'Stock inicial de datos semilla'),
(9,2,3,'AJUSTE','INICIAL',31,55,0,31,1705,'Stock inicial de datos semilla'),
(10,2,3,'AJUSTE','INICIAL',34,70,0,34,2380,'Stock inicial de datos semilla'),
(11,2,3,'AJUSTE','INICIAL',37,50,0,37,1850,'Stock inicial de datos semilla'),
(12,2,3,'AJUSTE','INICIAL',40,650,0,40,26000,'Stock inicial de datos semilla'),
(13,2,3,'AJUSTE','INICIAL',43,35,0,43,1505,'Stock inicial de datos semilla'),
(14,2,3,'AJUSTE','INICIAL',20,120,0,20,2400,'Stock inicial de datos semilla'),
(15,2,3,'AJUSTE','INICIAL',23,95,0,23,2185,'Stock inicial de datos semilla'),
(16,2,3,'AJUSTE','INICIAL',26,250,0,26,6500,'Stock inicial de datos semilla'),
(17,2,3,'AJUSTE','INICIAL',29,110,0,29,3190,'Stock inicial de datos semilla'),
(18,2,3,'AJUSTE','INICIAL',32,280,0,32,8960,'Stock inicial de datos semilla'),
(19,2,3,'AJUSTE','INICIAL',35,45,0,35,1575,'Stock inicial de datos semilla'),
(20,2,3,'AJUSTE','INICIAL',38,130,0,38,4940,'Stock inicial de datos semilla'),
(1,3,3,'AJUSTE','INICIAL',38,2000,0,38,76000,'Stock inicial de datos semilla'),
(2,3,3,'AJUSTE','INICIAL',41,40,0,41,1640,'Stock inicial de datos semilla'),
(3,3,3,'AJUSTE','INICIAL',44,55,0,44,2420,'Stock inicial de datos semilla'),
(4,3,3,'AJUSTE','INICIAL',21,250,0,21,5250,'Stock inicial de datos semilla'),
(5,3,3,'AJUSTE','INICIAL',24,320,0,24,7680,'Stock inicial de datos semilla'),
(6,3,3,'AJUSTE','INICIAL',27,90,0,27,2430,'Stock inicial de datos semilla'),
(7,3,3,'AJUSTE','INICIAL',30,100,0,30,3000,'Stock inicial de datos semilla'),
(8,3,3,'AJUSTE','INICIAL',33,500,0,33,16500,'Stock inicial de datos semilla'),
(9,3,3,'AJUSTE','INICIAL',36,55,0,36,1980,'Stock inicial de datos semilla'),
(10,3,3,'AJUSTE','INICIAL',39,70,0,39,2730,'Stock inicial de datos semilla'),
(11,3,3,'AJUSTE','INICIAL',42,50,0,42,2100,'Stock inicial de datos semilla'),
(12,3,3,'AJUSTE','INICIAL',45,650,0,45,29250,'Stock inicial de datos semilla'),
(13,3,3,'AJUSTE','INICIAL',22,35,0,22,770,'Stock inicial de datos semilla'),
(14,3,3,'AJUSTE','INICIAL',25,120,0,25,3000,'Stock inicial de datos semilla'),
(15,3,3,'AJUSTE','INICIAL',28,95,0,28,2660,'Stock inicial de datos semilla'),
(16,3,3,'AJUSTE','INICIAL',31,250,0,31,7750,'Stock inicial de datos semilla'),
(17,3,3,'AJUSTE','INICIAL',34,110,0,34,3740,'Stock inicial de datos semilla'),
(18,3,3,'AJUSTE','INICIAL',37,280,0,37,10360,'Stock inicial de datos semilla'),
(19,3,3,'AJUSTE','INICIAL',40,45,0,40,1800,'Stock inicial de datos semilla'),
(20,3,3,'AJUSTE','INICIAL',43,130,0,43,5590,'Stock inicial de datos semilla');

-- Compras de prueba. Los triggers incrementan stock, generan movimiento y kardex.
INSERT INTO compras
(id_proveedor,id_usuario,id_almacen,id_tipo_comprobante,serie,numero,fecha,subtotal,igv,total) VALUES
(1,3,1,4,'C001','000001','2026-07-01 09:15:00',4180.00,752.40,4932.40),
(3,3,1,4,'C001','000002','2026-07-03 10:30:00',6600.00,1188.00,7788.00),
(6,8,2,4,'C001','000003','2026-07-05 15:45:00',7600.00,1368.00,8968.00);

INSERT INTO detalle_compras (id_compra,id_producto,cantidad,costo_unitario,subtotal) VALUES
(1,1,2,1900.00,3800.00),
(1,2,10,38.00,380.00),
(2,4,10,240.00,2400.00),
(2,5,8,300.00,2400.00),
(2,6,20,90.00,1800.00),
(3,7,15,100.00,1500.00),
(3,8,10,500.00,5000.00),
(3,9,20,55.00,1100.00);

-- Ventas de prueba. Los triggers descuentan stock y conservan el costo histórico.
INSERT INTO ventas
(id_cliente,id_usuario,id_almacen,id_tipo_comprobante,serie,numero,fecha,subtotal,igv,total) VALUES
(1,2,1,1,'B001','000001','2026-07-06 10:00:00',2630.00,473.40,3103.40),
(4,2,1,1,'B001','000002','2026-07-07 11:20:00',830.00,149.40,979.40),
(6,7,2,2,'F001','000001','2026-07-08 14:10:00',1110.00,199.80,1309.80),
(7,7,1,1,'B001','000003','2026-07-09 16:40:00',550.00,99.00,649.00);

INSERT INTO detalle_ventas
(id_venta,id_producto,cantidad,costo_unitario,precio_unitario,subtotal) VALUES
(1,1,1,0.00,2500.00,2500.00),
(1,2,2,0.00,65.00,130.00),
(2,4,1,0.00,350.00,350.00),
(2,5,1,0.00,480.00,480.00),
(3,7,2,0.00,180.00,360.00),
(3,8,1,0.00,750.00,750.00),
(4,10,3,0.00,120.00,360.00),
(4,11,2,0.00,95.00,190.00);

INSERT INTO cajas (nombre,descripcion) VALUES
('Caja Principal','Caja de ventas principal'),
('Caja Secundaria','Caja auxiliar'),
('Caja Tienda','Caja ubicada en tienda central');

INSERT INTO cierres_caja
(id_caja,id_usuario_apertura,id_usuario_cierre,fecha_apertura,fecha_cierre,
 saldo_inicial,total_ingresos,total_egresos,saldo_sistema,saldo_fisico,diferencia,estado,observacion) VALUES
(1,4,4,'2026-07-06 08:00:00','2026-07-06 18:00:00',500.00,3103.40,100.00,3503.40,3503.40,0.00,'CERRADA','Turno cerrado sin diferencia'),
(2,4,NULL,'2026-07-07 08:00:00',NULL,300.00,979.40,0.00,1279.40,NULL,NULL,'ABIERTA','Turno abierto para pruebas'),
(3,4,NULL,'2026-07-08 08:00:00',NULL,400.00,1958.80,0.00,2358.80,NULL,NULL,'ABIERTA','Turno de tienda abierto');

INSERT INTO movimientos_caja
(id_cierre_caja,id_caja,id_usuario,id_pago,tipo_movimiento,monto,descripcion,fecha) VALUES
(1,1,4,NULL,'APERTURA',500.00,'Apertura de caja','2026-07-06 08:00:00'),
(1,1,4,NULL,'EGRESO',100.00,'Compra de útiles','2026-07-06 12:00:00'),
(2,2,4,NULL,'APERTURA',300.00,'Apertura de caja','2026-07-07 08:00:00'),
(3,3,4,NULL,'APERTURA',400.00,'Apertura de caja','2026-07-08 08:00:00');

INSERT INTO pagos
(id_venta,id_metodo_pago,id_usuario,id_cierre_caja,fecha,monto,referencia,observacion) VALUES
(1,1,4,1,'2026-07-06 10:05:00',3103.40,NULL,'Pago total en efectivo'),
(2,2,4,2,'2026-07-07 11:25:00',500.00,'YAPE-0707-001','Primer pago'),
(2,1,4,2,'2026-07-07 11:26:00',479.40,NULL,'Saldo en efectivo'),
(3,4,4,3,'2026-07-08 14:15:00',1309.80,'TRX-20260708-001','Transferencia total'),
(4,5,4,3,'2026-07-09 16:45:00',649.00,'VISA-4589','Pago con tarjeta');

INSERT INTO movimientos_caja
(id_cierre_caja,id_caja,id_usuario,id_pago,tipo_movimiento,monto,descripcion,fecha)
SELECT p.id_cierre_caja,cc.id_caja,p.id_usuario,p.id_pago,'INGRESO',p.monto,
       CONCAT('Ingreso por pago de venta ',p.id_venta),p.fecha
FROM pagos p
JOIN cierres_caja cc ON cc.id_cierre_caja=p.id_cierre_caja;

INSERT INTO sesiones (id_usuario,token_hash,ip,navegador,estado) VALUES
(1,SHA2('TOKEN_ADMIN_DEMO_001',256),'127.0.0.1','Google Chrome','ACTIVA'),
(2,SHA2('TOKEN_VENDEDOR_DEMO_001',256),'127.0.0.1','Microsoft Edge','CERRADA');

INSERT INTO auditoria
(id_usuario,tabla_afectada,registro_id,accion,descripcion,ip) VALUES
(1,'usuarios',1,'LOGIN','Inicio de sesión del administrador','127.0.0.1'),
(3,'compras',1,'INSERT','Registro de compra C001-000001','127.0.0.1'),
(3,'compras',2,'INSERT','Registro de compra C001-000002','127.0.0.1'),
(8,'compras',3,'INSERT','Registro de compra C001-000003','127.0.0.1'),
(2,'ventas',1,'INSERT','Registro de venta B001-000001','127.0.0.1'),
(2,'ventas',2,'INSERT','Registro de venta B001-000002','127.0.0.1'),
(7,'ventas',3,'INSERT','Registro de venta F001-000001','127.0.0.1'),
(7,'ventas',4,'INSERT','Registro de venta B001-000003','127.0.0.1'),
(4,'pagos',1,'INSERT','Registro de pago de la venta 1','127.0.0.1');

COMMIT;

-- =====================================================================
-- VALIDACIÓN DE DATOS SEMILLA
-- =====================================================================
SELECT 'empresa' AS entidad, COUNT(*) AS total FROM empresa
UNION ALL SELECT 'roles',COUNT(*) FROM roles
UNION ALL SELECT 'usuarios',COUNT(*) FROM usuarios
UNION ALL SELECT 'clientes',COUNT(*) FROM clientes
UNION ALL SELECT 'proveedores',COUNT(*) FROM proveedores
UNION ALL SELECT 'productos',COUNT(*) FROM productos
UNION ALL SELECT 'almacenes',COUNT(*) FROM almacenes
UNION ALL SELECT 'compras',COUNT(*) FROM compras
UNION ALL SELECT 'ventas',COUNT(*) FROM ventas
UNION ALL SELECT 'pagos',COUNT(*) FROM pagos;

-- Consultas rápidas:
-- SELECT * FROM vw_usuarios_roles;
-- SELECT * FROM vw_stock_actual ORDER BY almacen,producto;
-- SELECT * FROM vw_ventas_detalladas;
-- SELECT * FROM vw_saldos_ventas;
-- SHOW TRIGGERS FROM db_ventas;
-- SHOW PROCEDURE STATUS WHERE Db='db_ventas';

-- FIN DEL SCRIPT
