'use strict';
const path = require('path');

// ─── Fixtures de BD  ──────────────────────────────────────────────────────

const FIXTURES = {

    wallet: {
        id: 224, user_id: 29, ammount: 111.00, active: 1
    },

    walletTransactions: [
        { id: 1015, wallet_id: 224, user_id: 29, order_id: 418049, created: '2025-03-29 21:56:29', ammount: 3.00,  type: 'in',  info: 'Valor de la compra: $230.00, Minimo de compra: $200.00, Tipo: Porcentaje (%1)' },
        { id: 944,  wallet_id: 224, user_id: 29, order_id: 417376, created: '2025-03-27 13:46:36', ammount: 2.00,  type: 'in',  info: 'Valor de la compra: $225.00, Minimo de compra: $200.00, Tipo: Porcentaje (%1)' },
        { id: 812,  wallet_id: 224, user_id: 29, order_id: 416065, created: '2025-03-21 22:10:22', ammount: 5.00,  type: 'in',  info: 'Valor de la compra: $280.00, Minimo de compra: $200.00, Tipo: Porcentaje (%1)' },
    ],

    walletCalculation: {
        ammount: 3.00,
        info: 'Valor de la compra: $230.00, Minimo de compra: $200.00, Tipo: Porcentaje (%1)'
    },

    rider: {
        user_id: 10146, first_name: 'Antonio', last_name: 'Castro',
        email: 'antonio100914@hotmail.com', phone: '9622192714',
        id_rider_type: 1, rider_cuota: 30, role: 'rider',
        block: 0, tax_id: 1, profile_img: null, registration_card: null,
        plate_number: null, rfc: ''
    },

    riderLogin: {
        user_id: 10146, password: '$2b$10$hash_example',
        first_name: 'Antonio', last_name: 'Castro',
        phone: '9622192714', email: 'antonio100914@hotmail.com',
        profile_img: null, device_token: 'fcm_token_example',
        id_rider_type: 1, descripcion: 'Motorizado',
        block: 0, online: 0, tax_id: 1,
        callcenter: '9621234567', allowPayOnline: 1,
        registration_card: null, plate_number: null, rfc: ''
    },

    restaurantLogin: {
        user_id: 1, restaurant_id: 1, password: '$2b$10$hash_example',
        name: 'Wings Army Kafeto', phone: '6286106',
        email: 'wingsarmykafeto@gmail.com', image: 'app/webroot/uploads/1/5f01212a3318b.png',
        cover_image: null, device_token: 'fcm_restaurant_token',
        block: 0, online: 1, callcenter: '9621234567',
        bit_empaque: 0, latitud: 14.9, longitud: -92.2667,
        tax_id: 1, city: 'Tapachula', allowPayOnline: 1,
        has_agreement: 0, subrole: null
    },

    user: {
        id: 267, email: 'cliente@example.com', password: '$2b$10$hash_example',
        salt: null, active: 1, block: 0, created: '2021-01-01 00:00:00',
        token: null, role: 'user', subrole: null,
        user_id: 267, block_comment: null, customer_id: null,
        count_reject: 0, last_reject: null
    },

    userInfo: {
        user_id: 267, first_name: 'Juan', last_name: 'Pérez',
        phone: '+529991234567', email: 'cliente@example.com',
        device_token: 'user_fcm_token', block: 0,
        latitud: 14.9, longitud: -92.27, defaultAddress: 35531
    },

    address: {
        id: 35531, street: 'Walmart', apartment: null,
        city: '.', state: '.', zip: null, country: null,
        lat: 0.0, long: 0.0, instructions: null,
        default: 0, created: '2022-01-01', user_id: 267,
        tax_id: 0, simple: null
    },

    restaurantRating: {
        star: 5, comment: '-Sin comentarios-',
        created: '2026-05-18 12:00:34', user_id: 22952
    },

    riderRating: {
        order_id: 142836, star: 5,
        comment: '-Sin comentarios-', created: '2022-07-10 11:35:24'
    },

    notificationRestaurant: {
        tax_id: 1, user_id: 1,
        device_token: 'd_EScyQMTzCT5i4C9nKusO:APA91bFejqCix2yNum7CoU7wG1XtDQ'
    },

    order: {
        id: 510167, user_id: 21539, status: 2, restaurant_id: 16,
        price: 70.00, delivery_fee: 70.00, sub_total: 0.00,
        created: '2026-06-05 15:58:50', hotel_accepted: 1, bit_mandado: 1
    },

    coupon: {
        id: 643, restaurant_id: 656, coupon_code: 'PAPIPOLLO',
        discount: 15, expire_date: '2026-09-30', limit_users: 30, visible: 1,
        coupons_used: 5
    },

};

// ─── Helper: mock de sails.sendNativeQuery ────────────────────────────────────

/**
 * Crea un mock de sails.sendNativeQuery que devuelve { rows: data }
 * y captura la query y los params para poder inspeccionarlos en los tests.
 */
function makeSailsMock(responseRows = []) {
    const calls = [];
    const sendNativeQuery = jest.fn().mockImplementation((sql, params) => {
        calls.push({ sql, params });
        return Promise.resolve({ rows: responseRows });
    });
    return {
        sendNativeQuery,
        calls,
        log: { info: jest.fn(), warn: jest.fn(), error: jest.fn(), verbose: jest.fn() },
        config: { querys: {} },
    };
}

/**
 * Carga las queries de config y las inyecta en sails.config.querys
 */
function loadQueryConfig() {
    process.env.DB_NAME = 'meepDelivey';
    const modules = [
        'wallet', 'address', 'notifications', 'order',
        'restaurant', 'restaurantcoupon', 'restaurantrating',
        'riderrating', 'reports', 'rider', 'tax', 'user'
    ];
    const config = {};
    modules.forEach(m => {
        const { querys } = require(path.resolve(__dirname, `../../config/querys/${m}`));
        Object.assign(config, querys);
    });
    return config;
}

// ─────────────────────────────────────────────────────────────────────────────
// WalletService — setWallet
// ─────────────────────────────────────────────────────────────────────────────

describe('WalletService — mocks con datos reales de BD', () => {

    let WalletService;
    let WalletTransactionService;
    let sailsMock;

    beforeEach(() => {
        jest.resetModules();

        sailsMock = makeSailsMock([FIXTURES.walletCalculation]);
        sailsMock.config.querys = loadQueryConfig();

        global.sails = sailsMock;

        // Mock Waterline models
        global.Wallet = {
            findOne: jest.fn().mockResolvedValue(FIXTURES.wallet),
            create : jest.fn().mockReturnValue({ fetch: jest.fn().mockResolvedValue(FIXTURES.wallet) }),
        };

        global.WalletTransaction = {
            create: jest.fn().mockReturnValue({ fetch: jest.fn().mockResolvedValue({ id: 1 }) }),
        };

        global.WalletTransactionService = {
            createWalletTransaction: jest.fn().mockResolvedValue({ id: 1 }),
        };

        WalletService = require(path.resolve(__dirname, '../../api/services/WalletService'));
    });

    afterEach(() => {
        delete global.sails;
        delete global.Wallet;
        delete global.WalletTransaction;
        delete global.WalletTransactionService;
    });

    it('setWallet movement=in: llama a UPDATE_WALLET_IN con [ammount, user_id]', async () => {
        await WalletService.setWallet({
            order_id: 418049,
            user_id: 29,
            move_ammount: null,
            movement: 'in',
            force_set: 0,
        });

        const calls = sailsMock.sendNativeQuery.mock.calls;

        // Primera llamada: GET_WALLET_CALCULATION_FROM_ORDER con [order_id]
        expect(calls[0][0]).toBe(sailsMock.config.querys.wallet.GET_WALLET_CALCULATION_FROM_ORDER);
        expect(calls[0][1]).toEqual([418049]);

        // Segunda llamada: UPDATE_WALLET_IN con [ammount, user_id]
        expect(calls[1][0]).toBe(sailsMock.config.querys.wallet.UPDATE_WALLET_IN);
        expect(calls[1][1]).toEqual([FIXTURES.walletCalculation.ammount, 29]);

        // Verifica que UPDATE_WALLET_IN usa $1 (ammount) y $2 (user_id)
        expect(calls[1][0]).toContain('ammount + $1');
        expect(calls[1][0]).toContain('user_id = $2');
    });

    it('setWallet movement=out: llama a UPDATE_WALLET_OUT con GREATEST (no saldo negativo)', async () => {
        await WalletService.setWallet({
            order_id: 418049,
            user_id: 29,
            move_ammount: 50.00,
            movement: 'out',
            force_set: 1,
        });

        const updateCall = sailsMock.sendNativeQuery.mock.calls[0];
        expect(updateCall[0]).toBe(sailsMock.config.querys.wallet.UPDATE_WALLET_OUT);
        expect(updateCall[0].toUpperCase()).toContain('GREATEST');
        expect(updateCall[1]).toEqual([50.00, 29]);
    });

    it('setWallet movement=set: llama a UPDATE_WALLET_SET con [ammount, user_id]', async () => {
        await WalletService.setWallet({
            order_id: null,
            user_id: 29,
            move_ammount: 100.00,
            movement: 'set',
            force_set: 1,
        });

        const updateCall = sailsMock.sendNativeQuery.mock.calls[0];
        expect(updateCall[0]).toBe(sailsMock.config.querys.wallet.UPDATE_WALLET_SET);
        expect(updateCall[0]).toContain('ammount = $1');
        expect(updateCall[1]).toEqual([100.00, 29]);
    });

    it('setWallet devuelve el wallet actualizado con shape real de BD', async () => {
        const result = await WalletService.setWallet({
            order_id: 418049,
            user_id: 29,
            move_ammount: null,
            movement: 'in',
            force_set: 0,
        });

        // El resultado debe tener las columnas reales de la tabla wallet
        expect(result).toMatchObject({
            id: 224,
            user_id: 29,
            ammount: 111.00,
            active: 1,
        });
    });

    it('setWallet crea wallet nuevo si no existe (wallet = null)', async () => {
        global.Wallet.findOne = jest.fn()
            .mockResolvedValueOnce(null)            // primera llamada: no existe
            .mockResolvedValueOnce(FIXTURES.wallet); // segunda: después de crear

        const result = await WalletService.setWallet({
            order_id: 418049,
            user_id: 29,
            move_ammount: null,
            movement: 'in',
            force_set: 0,
        });

        expect(global.Wallet.create).toHaveBeenCalledWith(
            expect.objectContaining({ user_id: 29, active: 1 })
        );
        expect(result).toBeDefined();
    });

    it('getWallet devuelve el wallet del usuario con shape real', async () => {
        const result = await WalletService.getWallet({ user_id: 29 });

        expect(result).toMatchObject({
            id: 224,
            user_id: 29,
            ammount: 111.00,
            active: 1,
        });
    });
});

// ─────────────────────────────────────────────────────────────────────────────
// UserController — login (las queries de login se usan en el controller)
// ─────────────────────────────────────────────────────────────────────────────

describe('UserController.login — mocks con datos reales de BD', () => {

    let UserController;
    let sailsMock;
    let req; let res;

    function makeResMock() {
        return {
            ok       : jest.fn(),
            notFound : jest.fn(),
            badRequest: jest.fn(),
            serverError: jest.fn(),
        };
    }

    function makeReqMock(params) {
        return { allParams: () => params };
    }

    beforeEach(() => {
        jest.resetModules();

        sailsMock = makeSailsMock([FIXTURES.riderLogin]);
        sailsMock.config.querys = loadQueryConfig();
        sailsMock.__  = jest.fn().mockReturnValue('error message');
        sailsMock.helpers = {
            passwords: {
                checkPassword: jest.fn().mockResolvedValue(true),
            },
            generateNewJwtToken: jest.fn().mockResolvedValue('jwt.token.mock'),
        };

        global.sails = sailsMock;
        res = makeResMock();

        UserController = require(path.resolve(__dirname, '../../api/controllers/UserController'));
    });

    afterEach(() => {
        delete global.sails;
    });

    it('login rider: sendNativeQuery recibe RIDER_SELECT_BY_EMAIL con [email]', async () => {
        req = makeReqMock({
            email: 'antonio100914@hotmail.com',
            password: 'plainpassword',
            role: 'rider',
            device_token: 'tok',
        });

        await UserController.login(req, res);

        const firstCall = sailsMock.sendNativeQuery.mock.calls[0];
        expect(firstCall[0]).toBe(sailsMock.config.querys.user.RIDER_SELECT_BY_EMAIL);
        expect(firstCall[1]).toEqual(['antonio100914@hotmail.com']);
        expect(firstCall[0]).toContain('\'rider\'');
        expect(firstCall[0]).toContain('U.block = 0');
    });

    it('login hotel: sendNativeQuery recibe RESTAURANT_SELECT_BY_EMAIL con [email]', async () => {
        sailsMock.sendNativeQuery.mockResolvedValueOnce({ rows: [FIXTURES.restaurantLogin] });
        req = makeReqMock({
            email: 'wingsarmykafeto@gmail.com',
            password: 'plainpassword',
            role: 'hotel',
            device_token: 'tok',
        });

        await UserController.login(req, res);

        const firstCall = sailsMock.sendNativeQuery.mock.calls[0];
        expect(firstCall[0]).toBe(sailsMock.config.querys.user.RESTAURANT_SELECT_BY_EMAIL);
        expect(firstCall[1]).toEqual(['wingsarmykafeto@gmail.com']);
        expect(firstCall[0]).toContain('\'hotel\'');
    });

    it('login user: sendNativeQuery recibe USER_SELECT_BY_EMAIL con [email]', async () => {
        sailsMock.sendNativeQuery.mockResolvedValueOnce({ rows: [FIXTURES.userInfo] });
        req = makeReqMock({
            email: 'cliente@example.com',
            password: 'plainpassword',
            role: 'user',
            device_token: 'tok',
        });

        await UserController.login(req, res);

        const firstCall = sailsMock.sendNativeQuery.mock.calls[0];
        expect(firstCall[0]).toBe(sailsMock.config.querys.user.USER_SELECT_BY_EMAIL);
        expect(firstCall[0]).toContain('\'user\'');
    });

    it('login devuelve res.notFound() cuando el usuario no existe (rows vacío)', async () => {
        sailsMock.sendNativeQuery.mockResolvedValueOnce({ rows: [] });
        req = makeReqMock({
            email: 'noexiste@example.com',
            password: 'pass',
            role: 'rider',
        });

        await UserController.login(req, res);

        expect(res.notFound).toHaveBeenCalled();
    });

    it('login devuelve res.badRequest() si falta email o password', async () => {
        req = makeReqMock({ email: '', password: '', role: 'rider' });

        await UserController.login(req, res);

        expect(res.badRequest).toHaveBeenCalled();
        expect(sailsMock.sendNativeQuery).not.toHaveBeenCalled();
    });
});

// ─────────────────────────────────────────────────────────────────────────────
// WalletTransactionService — getWalletTransactionsByDates
// ─────────────────────────────────────────────────────────────────────────────

describe('WalletTransactionService — mocks con datos reales de BD', () => {

    let WalletTransactionService;
    let sailsMock;

    beforeEach(() => {
        jest.resetModules();

        sailsMock = makeSailsMock(FIXTURES.walletTransactions);
        sailsMock.config.querys = loadQueryConfig();
        global.sails = sailsMock;

        global.WalletTransaction = {
            create: jest.fn().mockReturnValue({ fetch: jest.fn().mockResolvedValue({ id: 1 }) }),
        };

        WalletTransactionService = require(path.resolve(__dirname, '../../api/services/WalletTransactionService'));
    });

    afterEach(() => {
        delete global.sails;
        delete global.WalletTransaction;
    });

    it('getWalletTransactionsbyDates: sendNativeQuery recibe query y [wallet_id, start_date, end_date]', async () => {
        // Nombre real: getWalletTransactionsbyDates (b minúscula)
        // Params reales: { wallet_id, start_date, end_date }
        await WalletTransactionService.getWalletTransactionsbyDates({
            wallet_id: 224,
            start_date: '2025-03-01',
            end_date: '2025-03-31',
        });

        const call = sailsMock.sendNativeQuery.mock.calls[0];
        expect(call[0]).toBe(sailsMock.config.querys.wallet.GET_WALLET_TRANSACTION_BY_DATES);
        expect(call[1]).toEqual([224, '2025-03-01', '2025-03-31']);
        expect(call[0]).toContain('wallet_transaction');
        expect(call[0]).toContain('ORDER BY');
    });

    it('getWalletTransactionsbyDates devuelve array con shape real de BD', async () => {
        const result = await WalletTransactionService.getWalletTransactionsbyDates({
            wallet_id: 224,
            start_date: '2025-03-01',
            end_date: '2025-03-31',
        });

        expect(Array.isArray(result)).toBe(true);
        expect(result.length).toBeGreaterThan(0);
        expect(result[0]).toMatchObject({
            wallet_id: expect.any(Number),
            user_id: expect.any(Number),
            ammount: expect.any(Number),
            type: expect.stringMatching(/^(in|out|set|return)$/),
        });
    });
});

// ─────────────────────────────────────────────────────────────────────────────
// Queries SQL — verificación de shapes de respuesta
// ─────────────────────────────────────────────────────────────────────────────

describe('Query shapes — columnas esperadas en respuesta de BD real', () => {

    /**
     * Estos tests verifican que las columnas que los servicios esperan
     * coincidan con las que la BD realmente devuelve.
     */

    it('wallet: columnas id, user_id, ammount, active', () => {
        const wallet = FIXTURES.wallet;
        expect(wallet).toHaveProperty('id');
        expect(wallet).toHaveProperty('user_id');
        expect(wallet).toHaveProperty('ammount');
        expect(wallet).toHaveProperty('active');
        expect(typeof wallet.ammount).toBe('number');
    });

    it('wallet_transaction: columnas id, wallet_id, user_id, order_id, ammount, type, info', () => {
        const tx = FIXTURES.walletTransactions[0];
        expect(tx).toHaveProperty('id');
        expect(tx).toHaveProperty('wallet_id');
        expect(tx).toHaveProperty('user_id');
        expect(tx).toHaveProperty('order_id');
        expect(tx).toHaveProperty('ammount');
        expect(tx).toHaveProperty('type');
        expect(tx).toHaveProperty('info');
        expect(['in', 'out', 'set', 'return']).toContain(tx.type);
    });

    it('wallet_calculation: columnas ammount e info (resultado de cálculo)', () => {
        const calc = FIXTURES.walletCalculation;
        expect(calc).toHaveProperty('ammount');
        expect(calc).toHaveProperty('info');
        expect(typeof calc.ammount).toBe('number');
        expect(typeof calc.info).toBe('string');
        expect(calc.info).toMatch(/Valor de la compra/);
    });

    it('rider login: columnas user_id, password, first_name, last_name, tax_id, block, role', () => {
        const rider = FIXTURES.riderLogin;
        expect(rider).toHaveProperty('user_id');
        expect(rider).toHaveProperty('password');
        expect(rider).toHaveProperty('first_name');
        expect(rider).toHaveProperty('last_name');
        expect(rider).toHaveProperty('tax_id');
        expect(rider).toHaveProperty('block');
        expect(rider.block).toBe(0);
    });

    it('restaurant login: columnas restaurant_id, name, tax_id, city, online', () => {
        const rest = FIXTURES.restaurantLogin;
        expect(rest).toHaveProperty('restaurant_id');
        expect(rest).toHaveProperty('name');
        expect(rest).toHaveProperty('tax_id');
        expect(rest).toHaveProperty('city');
        expect(rest).toHaveProperty('online');
    });

    it('user: columnas id, email, role, block, count_reject', () => {
        const user = FIXTURES.user;
        expect(user).toHaveProperty('id');
        expect(user).toHaveProperty('email');
        expect(user).toHaveProperty('role');
        expect(user.role).toBe('user');
        expect(user).toHaveProperty('block');
        expect(user).toHaveProperty('count_reject');
    });

    it('order: columnas id, user_id, status, restaurant_id, price, delivery_fee', () => {
        const order = FIXTURES.order;
        expect(order).toHaveProperty('id');
        expect(order).toHaveProperty('user_id');
        expect(order).toHaveProperty('status');
        expect(order).toHaveProperty('restaurant_id');
        expect(order).toHaveProperty('price');
        expect(order).toHaveProperty('delivery_fee');
        expect([1, 2, 3, 4, 5]).toContain(order.status);
    });

    it('restaurant_rating: columnas star, comment, created, user_id', () => {
        const rating = FIXTURES.restaurantRating;
        expect(rating).toHaveProperty('star');
        expect(rating).toHaveProperty('comment');
        expect(rating).toHaveProperty('created');
        expect(rating).toHaveProperty('user_id');
        expect(rating.star).toBeGreaterThanOrEqual(1);
        expect(rating.star).toBeLessThanOrEqual(5);
    });

    it('rider_rating: columnas order_id, star, comment, created', () => {
        const rating = FIXTURES.riderRating;
        expect(rating).toHaveProperty('order_id');
        expect(rating).toHaveProperty('star');
        expect(rating).toHaveProperty('comment');
        expect(rating).toHaveProperty('created');
    });

    it('coupon: columnas id, restaurant_id, coupon_code, discount, expire_date, limit_users', () => {
        const coupon = FIXTURES.coupon;
        expect(coupon).toHaveProperty('id');
        expect(coupon).toHaveProperty('restaurant_id');
        expect(coupon).toHaveProperty('coupon_code');
        expect(coupon).toHaveProperty('discount');
        expect(coupon).toHaveProperty('expire_date');
        expect(coupon).toHaveProperty('limit_users');
        expect(typeof coupon.discount).toBe('number');
        expect(coupon.discount).toBeGreaterThan(0);
    });

    it('notification device_token: columna requerida por FCM existe y no está vacía', () => {
        const notif = FIXTURES.notificationRestaurant;
        expect(notif).toHaveProperty('device_token');
        expect(notif).toHaveProperty('user_id');
        expect(notif).toHaveProperty('tax_id');
        expect(notif.device_token.length).toBeGreaterThan(10);
    });
});

// ─────────────────────────────────────────────────────────────────────────────
// sendNativeQuery — parámetros correctos por query
// ─────────────────────────────────────────────────────────────────────────────

describe('sendNativeQuery — orden y tipo de parámetros por query', () => {

    /**
     * Verifica que el número de valores que el servicio pasa a sendNativeQuery
     * coincida con el número de placeholders $N en la query.
     */

    process.env.DB_NAME = 'meepDelivey';
    const { querys: walletQ }  = require(path.resolve(__dirname, '../../config/querys/wallet'));
    const { querys: riderQ }   = require(path.resolve(__dirname, '../../config/querys/rider'));
    const { querys: orderQ }   = require(path.resolve(__dirname, '../../config/querys/order'));
    const { querys: userQ }    = require(path.resolve(__dirname, '../../config/querys/user'));
    const { querys: reportQ }  = require(path.resolve(__dirname, '../../config/querys/reports'));

    function maxPlaceholder(sql) {
        const matches = sql.match(/\$[1-9][0-9]*/g) || [];
        if (matches.length === 0) { return 0; }
        return Math.max(...matches.map(m => parseInt(m.slice(1))));
    }

    const paramsCases = [
        // [queryName, sql, expectedParams]
        ['UPDATE_WALLET_IN',                   walletQ.wallet.UPDATE_WALLET_IN,                  [50.00, 29]],
        ['UPDATE_WALLET_OUT',                  walletQ.wallet.UPDATE_WALLET_OUT,                 [50.00, 29]],
        ['UPDATE_WALLET_SET',                  walletQ.wallet.UPDATE_WALLET_SET,                 [100.00, 29]],
        ['GET_WALLET_TRANSACTION_BY_DATES',    walletQ.wallet.GET_WALLET_TRANSACTION_BY_DATES,   [224, '2025-03-01', '2025-03-31']],
        ['GET_WALLET_CALCULATION_FROM_ORDER',  walletQ.wallet.GET_WALLET_CALCULATION_FROM_ORDER, [418049]],

        ['RIDER_SELECT_BY_EMAIL',   userQ.user.RIDER_SELECT_BY_EMAIL,      ['rider@example.com']],
        ['USER_SELECT_BY_EMAIL',    userQ.user.USER_SELECT_BY_EMAIL,        ['user@example.com']],
        ['ADMIN_SELECT_BY_EMAIL',   userQ.user.ADMIN_SELECT_BY_EMAIL,       ['admin@example.com']],
        ['ADMIN_SELECT_BY_ID',      userQ.user.ADMIN_SELECT_BY_ID,          [16]],
        ['GET_USER',                userQ.user.GET_USER,                    [267]],
        ['GET_USER_RIDER_BY_USERID',userQ.user.GET_USER_RIDER_BY_USERID,   [10146]],

        ['SELECT_ORDER_FITTED',       orderQ.order.SELECT_ORDER_FITTED,       [510167]],
        ['SELECT_CLIENT_RECORDS',     orderQ.order.SELECT_CLIENT_RECORDS,     [267, 0, 10]],
        ['SELECT_RIDER_OPEN_ORDERS',  orderQ.order.SELECT_RIDER_OPEN_ORDERS,  [10146]],
        ['SELECT_PENDING_ORDERS',     orderQ.order.SELECT_PENDING_ORDERS,     [1]],

        ['SELECT_RIDER',              riderQ.rider.SELECT_RIDER,              [10146]],
        ['SELECT_PROFILE_INDICATORS', riderQ.rider.SELECT_PROFILE_INDICATORS, [10146]],
        ['SELECT_IS_BLOCKED',         riderQ.rider.SELECT_IS_BLOCKED,         [10146]],
        ['SELECT_IS_OWENING_MONEY',   riderQ.rider.SELECT_IS_OWENING_MONEY,   [10146, '2025-03-31']],

        ['RPT_MENSUAL_LICENCIAS', reportQ.reports.RPT_MENSUAL_LICENCIAS, [1, 3, 2025]],
        ['RPT_ANUAL_INGRESOS',    reportQ.reports.RPT_ANUAL_INGRESOS,    [2025]],
        ['RPT_WEEK_RESUME',       reportQ.reports.RPT_WEEK_RESUME,       [1, '2025-03-01', '2025-03-07']],
        ['RPT_GRANJERO',          reportQ.reports.RPT_GRANJERO,          ['2025-03-01', '2025-03-07']],
        ['RPT_SERVICIOS',         reportQ.reports.RPT_SERVICIOS,         [16, '2025-03-01', '2025-03-07', 0]],
    ];

    paramsCases.forEach(([name, sql, params]) => {
        it(`${name}: params.length (${params.length}) == max placeholder ($${maxPlaceholder(sql)})`, () => {
            const max = maxPlaceholder(sql);
            // El número de params debe cubrir hasta el placeholder más alto
            expect(params.length).toBeGreaterThanOrEqual(max);
            // Ningún param debe ser undefined
            params.forEach((p, i) => {
                expect(p).not.toBeUndefined();
            });
        });
    });
});
