UNPKG

weaver-frontend-cli

Version:

🕷️ Weaver CLI - Generador completo de arquitectura Clean Architecture con parser OpenAPI avanzado para entidades CRUD y flujos de negocio complejos

2,249 lines 134 kB
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createBusinessFlow = createBusinessFlow;
const fs = __importStar(require("fs-extra"));
const path = __importStar(require("path"));
const chalk_1 = __importDefault(require("chalk"));
// Ruta base donde se ejecutará el comando
const DEFAULT_BASE_PATH = process.cwd(); // Directorio actual por defecto
const LOCAL_TEST_PATH = './test-output';
/**
 * Extrae el nombre de la operación del path, ignorando los parámetros de path (ej: {user_id})
 * Para /auth/delete-user-internal/{user_id} retorna "delete-user-internal"
 */
function getOperationNameFromPath(operationPath, fallback) {
    const segments = operationPath.split('/').filter(s => s.length > 0);
    // Buscar desde el final el primer segmento que NO sea un parámetro (que no tenga {})
    for (let i = segments.length - 1; i >= 0; i--) {
        const segment = segments[i];
        if (!segment.startsWith('{') && !segment.endsWith('}')) {
            return segment;
        }
    }
    // Si todos son parámetros, usar el fallback (operationId)
    return fallback;
}
async function createBusinessFlow(serviceName, basePath = DEFAULT_BASE_PATH, schema, targetApiName = 'platform') {
    console.log(chalk_1.default.blue(`📁 Generando flujo de negocio completo para ${serviceName} en: ${basePath}`));
    const serviceNameLower = serviceName.toLowerCase();
    const apiPrefix = '';
    const paths = {
        // DTOs para business
        domainModels: path.join(basePath, `${apiPrefix}domain/models/apis/${targetApiName}/business/${serviceNameLower}`),
        // Repositories para business
        domainRepositories: path.join(basePath, `${apiPrefix}domain/services/repositories/apis/${targetApiName}/business`),
        // Use Cases para business
        domainUseCases: path.join(basePath, `${apiPrefix}domain/services/use_cases/apis/${targetApiName}/business/${serviceNameLower}`),
        // Entities para business
        infraEntities: path.join(basePath, `${apiPrefix}infrastructure/entities/apis/${targetApiName}/business/${serviceNameLower}`),
        // Mappers para business
        infraMappers: path.join(basePath, `${apiPrefix}infrastructure/mappers/apis/${targetApiName}/business/${serviceNameLower}`),
        // Injection para mappers de business (ubicación correcta)
        injectionMappers: path.join(basePath, `${apiPrefix}infrastructure/mappers/apis/${targetApiName}/injection/business/${serviceNameLower}`),
        // Facades para business
        facades: path.join(basePath, `${apiPrefix}facade/apis/${targetApiName}/business`),
        // Injection para business Use Cases
        injectionUseCases: path.join(basePath, `${apiPrefix}domain/services/use_cases/apis/${targetApiName}/injection/business`),
        // Injection para business Facades
        injectionFacades: path.join(basePath, `${apiPrefix}facade/apis/${targetApiName}/injection/business`),
        // Infrastructure repositories para business
        infraRepositories: path.join(basePath, `${apiPrefix}infrastructure/repositories/apis/${targetApiName}/repositories/business/${serviceNameLower}`),
        // Injection para infrastructure repositories de business
        injectionRepositories: path.join(basePath, `${apiPrefix}infrastructure/repositories/apis/${targetApiName}/repositories/injection/business`)
    };
    try {
        // Crear directorios necesarios
        await createDirectoriesIfNotExists(paths);
        // Generar DTOs por operación
        await generateDomainDTOs(serviceName, paths, schema, targetApiName);
        // Generar repository interfaces por servicio
        await generateDomainRepositoryInterfaces(serviceName, paths, schema, targetApiName);
        // Generar use cases por operación
        await generateDomainUseCases(serviceName, paths, schema, targetApiName);
        // Generar entities por operación
        await generateInfrastructureEntities(serviceName, paths, schema, targetApiName);
        // Generar mappers por operación
        await generateInfrastructureMappers(serviceName, paths, schema, targetApiName);
        // Generar injection files por operación (en lugar de por servicio)
        await generateMapperInjectionPerOperation(serviceName, paths, schema, targetApiName);
        // Generar repositories de implementación por operación
        await generateInfrastructureRepositories(serviceName, paths, schema, targetApiName);
        // Generar injection para infrastructure repositories
        await generateRepositoryInjectionFiles(serviceName, paths, schema, targetApiName);
        // Generar facades por servicio
        await generateBusinessFacades(serviceName, paths, schema, targetApiName);
        // Generar archivos de inyección
        await generateBusinessInjectionFiles(serviceName, paths, schema, targetApiName);
        console.log(chalk_1.default.green(`✨ Flujo de negocio ${serviceName} generado exitosamente!`));
    }
    catch (error) {
        console.error(chalk_1.default.red('❌ Error generando archivos:'), error);
        throw error;
    }
}
async function generateInfrastructureEntities(serviceName, paths, schema, apiName = 'platform') {
    // Solo generar por operaciones de negocio (nunca legacy)
    if (schema?.businessOperations && schema.businessOperations.length > 0) {
        let exportStatements = [];
        for (const operation of schema.businessOperations) {
            const rawOperationName = getOperationNameFromPath(operation.path, operation.operationId.toLowerCase());
            const operationName = rawOperationName.replace(/_/g, '-');
            const operationFolder = path.join(paths.infraEntities, operationName);
            await fs.ensureDir(operationFolder);
            // --- Request Entity ---
            if (operation.fields && operation.fields.length > 0) {
                const requestInterface = generateBusinessEntityInterface(serviceName, operation, 'request');
                const requestFileName = `i-${serviceName.toLowerCase()}-${operationName}-request-entity.ts`;
                await fs.writeFile(path.join(operationFolder, requestFileName), requestInterface);
                const cleanOperationName = operationName
                    .replace(/_/g, '-')
                    .split('-')
                    .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
                    .join('');
                const requestInterfaceName = `I${toPascalCase(serviceName)}${cleanOperationName}RequestEntity`;
                exportStatements.push(`export { ${requestInterfaceName} } from './${operationName}/${requestFileName.replace('.ts', '')}';`);
                await generateNestedEntitiesForOperation(serviceName, operation, 'request', operationFolder, apiName, exportStatements, operationName);
            }
            // --- Response Entity ---
            if (operation.responseFields && operation.responseFields.length > 0) {
                const responseInterface = generateBusinessEntityInterface(serviceName, operation, 'response');
                const responseFileName = `i-${serviceName.toLowerCase()}-${operationName}-response-entity.ts`;
                await fs.writeFile(path.join(operationFolder, responseFileName), responseInterface);
                const cleanOperationName = operationName
                    .replace(/_/g, '-')
                    .split('-')
                    .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
                    .join('');
                const responseInterfaceName = `I${toPascalCase(serviceName)}${cleanOperationName}ResponseEntity`;
                exportStatements.push(`export { ${responseInterfaceName} } from './${operationName}/${responseFileName.replace('.ts', '')}';`);
                await generateNestedEntitiesForOperation(serviceName, operation, 'response', operationFolder, apiName, exportStatements, operationName);
            }
        }
        // Generar index.ts con export type (simplificado y mejor práctica) - INCREMENTAL
        const indexPath = path.join(paths.infraEntities, 'index.ts');
        const uniqueExports = new Set();
        const finalExports = [];
        // Leer exports existentes si el archivo ya existe
        if (await fs.pathExists(indexPath)) {
            try {
                const existingContent = await fs.readFile(indexPath, 'utf-8');
                const existingExports = existingContent.split('\n').filter(line => line.trim().startsWith('export'));
                existingExports.forEach(statement => {
                    const match = statement.match(/export (?:type )?\{ (\w+) \}/);
                    if (match) {
                        const interfaceName = match[1];
                        if (!uniqueExports.has(interfaceName)) {
                            uniqueExports.add(interfaceName);
                            // Asegurar que sea export type
                            const typeStatement = statement.includes('export type {') ? statement : statement.replace('export {', 'export type {');
                            finalExports.push(typeStatement);
                        }
                    }
                });
            }
            catch (error) {
                console.log(chalk_1.default.yellow(`⚠️  No se pudo leer index.ts existente: ${error}`));
            }
        }
        // Agregar nuevos exports
        exportStatements.forEach(statement => {
            const match = statement.match(/export \{ (\w+) \}/);
            if (match) {
                const interfaceName = match[1];
                if (!uniqueExports.has(interfaceName)) {
                    uniqueExports.add(interfaceName);
                    const typeStatement = statement.replace('export {', 'export type {');
                    finalExports.push(typeStatement);
                }
            }
        });
        const indexContent = finalExports.sort().join('\n') + '\n';
        await fs.writeFile(indexPath, indexContent);
        console.log(chalk_1.default.green(`✅ Index Entity: index.ts ${await fs.pathExists(indexPath) ? '(actualizado)' : ''}`));
    }
    else {
        // Si no hay operaciones de negocio, NO generar nada (nunca legacy)
        console.log(chalk_1.default.yellow('⚠️  No se generaron entities porque no hay operaciones de negocio detectadas.'));
        // Eliminar archivos legacy si existen
        const legacyFiles = [
            `i-${serviceName.toLowerCase()}-entity.ts`,
            `i-${serviceName.toLowerCase()}-save-entity.ts`,
            `i-${serviceName.toLowerCase()}-read-entity.ts`,
            `i-${serviceName.toLowerCase()}-update-entity.ts`,
            `i-${serviceName.toLowerCase()}-delete-entity.ts`,
            'index.ts'
        ];
        for (const file of legacyFiles) {
            const filePath = path.join(paths.infraEntities, file);
            if (await fs.pathExists(filePath)) {
                await fs.remove(filePath);
            }
        }
    }
}
async function generateInfrastructureMappers(serviceName, paths, schema, apiName = 'platform') {
    // Solo generar por operaciones de negocio
    if (schema?.businessOperations && schema.businessOperations.length > 0) {
        let exportStatements = [];
        for (const operation of schema.businessOperations) {
            const rawOperationName = getOperationNameFromPath(operation.path, operation.operationId.toLowerCase());
            const operationName = rawOperationName.replace(/_/g, '-');
            const operationFolder = path.join(paths.infraMappers, operationName);
            await fs.ensureDir(operationFolder);
            // --- Request Mapper ---
            if (operation.fields && operation.fields.length > 0) {
                const requestMapper = generateBusinessMapper(serviceName, operation, 'request', apiName);
                const requestFileName = `${serviceName.toLowerCase()}-${operationName}-request-mapper.ts`;
                await fs.writeFile(path.join(operationFolder, requestFileName), requestMapper);
                // Generar mappers para interfaces anidadas del request
                await generateNestedMappersForOperation(serviceName, operation, 'request', operationFolder, apiName, exportStatements, operationName);
                const cleanOperationName = operationName
                    .replace(/_/g, '-')
                    .split('-')
                    .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
                    .join('');
                const requestMapperName = `${toPascalCase(serviceName)}${cleanOperationName}RequestMapper`;
                exportStatements.push(`export { ${requestMapperName} } from './${operationName}/${serviceName.toLowerCase()}-${operationName}-request-mapper';`);
            }
            // --- Response Mapper ---
            if (operation.responseFields && operation.responseFields.length > 0) {
                const responseMapper = generateBusinessMapper(serviceName, operation, 'response', apiName);
                const responseFileName = `${serviceName.toLowerCase()}-${operationName}-response-mapper.ts`;
                await fs.writeFile(path.join(operationFolder, responseFileName), responseMapper);
                // Generar mappers para interfaces anidadas
                await generateNestedMappersForOperation(serviceName, operation, 'response', operationFolder, apiName, exportStatements, operationName);
                const cleanOperationName = operationName
                    .replace(/_/g, '-')
                    .split('-')
                    .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
                    .join('');
                const responseMapperName = `${toPascalCase(serviceName)}${cleanOperationName}ResponseMapper`;
                exportStatements.push(`export { ${responseMapperName} } from './${operationName}/${serviceName.toLowerCase()}-${operationName}-response-mapper';`);
            }
        }
        // Generar index.ts para mappers - INCREMENTAL
        const indexPath = path.join(paths.infraMappers, 'index.ts');
        const uniqueExports = new Set();
        const finalExports = [];
        // Leer exports existentes si el archivo ya existe
        if (await fs.pathExists(indexPath)) {
            try {
                const existingContent = await fs.readFile(indexPath, 'utf-8');
                const existingExports = existingContent.split('\n').filter(line => line.trim().startsWith('export'));
                existingExports.forEach(statement => {
                    const match = statement.match(/export \{ (\w+) \}/);
                    if (match) {
                        const mapperName = match[1];
                        if (!uniqueExports.has(mapperName)) {
                            uniqueExports.add(mapperName);
                            finalExports.push(statement);
                        }
                    }
                });
            }
            catch (error) {
                console.log(chalk_1.default.yellow(`⚠️  No se pudo leer index.ts existente: ${error}`));
            }
        }
        // Agregar nuevos exports
        exportStatements.forEach(statement => {
            const match = statement.match(/export \{ (\w+) \}/);
            if (match) {
                const mapperName = match[1];
                if (!uniqueExports.has(mapperName)) {
                    uniqueExports.add(mapperName);
                    finalExports.push(statement);
                }
            }
        });
        const indexContent = finalExports.sort().join('\n') + '\n';
        await fs.writeFile(indexPath, indexContent);
        console.log(chalk_1.default.green(`✅ Index Mapper: index.ts ${await fs.pathExists(indexPath) ? '(actualizado)' : ''}`));
    }
    else {
        console.log(chalk_1.default.yellow('⚠️  No se generaron mappers porque no hay operaciones de negocio detectadas.'));
    }
}
async function generateDomainDTOs(serviceName, paths, schema, apiName = 'platform') {
    // Solo generar por operaciones de negocio
    if (schema?.businessOperations && schema.businessOperations.length > 0) {
        let exportStatements = [];
        for (const operation of schema.businessOperations) {
            const rawOperationName = getOperationNameFromPath(operation.path, operation.operationId.toLowerCase());
            const operationName = rawOperationName.replace(/_/g, '-');
            const operationFolder = path.join(paths.domainModels, operationName);
            await fs.ensureDir(operationFolder);
            // --- Request DTO ---
            if (operation.fields && operation.fields.length > 0) {
                const requestDTO = generateBusinessDTO(serviceName, operation, 'request', apiName);
                const requestFileName = `i-${serviceName.toLowerCase()}-${operationName}-request-dto.ts`;
                await fs.writeFile(path.join(operationFolder, requestFileName), requestDTO);
                // Generar DTOs para interfaces anidadas de request
                await generateNestedDTOsForOperation(serviceName, operation, 'request', operationFolder, apiName, exportStatements, operationName);
                const cleanOperationName = operationName
                    .replace(/_/g, '-')
                    .split('-')
                    .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
                    .join('');
                const requestDTOName = `I${toPascalCase(serviceName)}${cleanOperationName}RequestDTO`;
                exportStatements.push(`export { ${requestDTOName} } from './${operationName}/${requestFileName.replace('.ts', '')}';`);
            }
            // --- Response DTO ---
            if (operation.responseFields && operation.responseFields.length > 0) {
                const responseDTO = generateBusinessDTO(serviceName, operation, 'response', apiName);
                const responseFileName = `i-${serviceName.toLowerCase()}-${operationName}-response-dto.ts`;
                await fs.writeFile(path.join(operationFolder, responseFileName), responseDTO);
                // Generar DTOs para interfaces anidadas con patrón correcto
                await generateNestedDTOsForOperation(serviceName, operation, 'response', operationFolder, apiName, exportStatements, operationName);
                const cleanOperationName = operationName
                    .replace(/_/g, '-')
                    .split('-')
                    .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
                    .join('');
                const baseResponseDTOName = `I${toPascalCase(serviceName)}${cleanOperationName}ResponseDTO`;
                const responseDTOName = operation.isResponseArray ? `${baseResponseDTOName}[]` : baseResponseDTOName;
                exportStatements.push(`export { ${baseResponseDTOName} } from './${operationName}/${responseFileName.replace('.ts', '')}';`);
            }
        }
        // Generar index.ts para DTOs con export type (simplificado y mejor práctica) - INCREMENTAL
        const indexPath = path.join(paths.domainModels, 'index.ts');
        const uniqueExports = new Set();
        const finalExports = [];
        // Leer exports existentes si el archivo ya existe
        if (await fs.pathExists(indexPath)) {
            try {
                const existingContent = await fs.readFile(indexPath, 'utf-8');
                const existingExports = existingContent.split('\n').filter(line => line.trim().startsWith('export'));
                existingExports.forEach(statement => {
                    const match = statement.match(/export (?:type )?\{ (\w+) \}/);
                    if (match) {
                        const interfaceName = match[1];
                        if (!uniqueExports.has(interfaceName)) {
                            uniqueExports.add(interfaceName);
                            // Asegurar que sea export type
                            const typeStatement = statement.includes('export type {') ? statement : statement.replace('export {', 'export type {');
                            finalExports.push(typeStatement);
                        }
                    }
                });
            }
            catch (error) {
                console.log(chalk_1.default.yellow(`⚠️  No se pudo leer index.ts existente: ${error}`));
            }
        }
        // Agregar nuevos exports
        exportStatements.forEach(statement => {
            const match = statement.match(/export \{ (\w+) \}/);
            if (match) {
                const interfaceName = match[1];
                if (!uniqueExports.has(interfaceName)) {
                    uniqueExports.add(interfaceName);
                    const typeStatement = statement.replace('export {', 'export type {');
                    finalExports.push(typeStatement);
                }
            }
        });
        const indexContent = finalExports.sort().join('\n') + '\n';
        await fs.writeFile(indexPath, indexContent);
        console.log(chalk_1.default.green(`✅ Index DTO: index.ts ${await fs.pathExists(indexPath) ? '(actualizado)' : ''}`));
    }
    else {
        console.log(chalk_1.default.yellow('⚠️  No se generaron DTOs porque no hay operaciones de negocio detectadas.'));
    }
}
async function generateNestedMappersForOperation(serviceName, operation, type, operationFolder, apiName, exportStatements, operationName) {
    const fields = type === 'request' ? operation.fields : operation.responseFields;
    const generated = new Set();
    function processNestedFields(field) {
        // Solo generar mappers para interfaces, NO para enums (los enums se mapean directamente)
        if (field.type && !['string', 'number', 'boolean', 'any', 'object', 'array'].includes(field.type) && !field.isEnum && !generated.has(field.type)) {
            generated.add(field.type);
            // Generar mapper individual para cada interface anidada
            const nestedMapper = generateIndividualNestedMapper(field.type, field, apiName, serviceName, operationName, type);
            // Aplicar el patrón correcto: <flujo>-<proceso>-<tipo>-<request/response>-mapper.ts
            const serviceNameKebab = serviceName.toLowerCase();
            const operationKebab = operationName.replace(/_/g, '-');
            // Limpiar el tipo para obtener solo el nombre base sin sufijos Login/Response 
            // pero preservando Response/Request cuando es la operación actual
            let cleanType = field.type;
            const currentOperationClean = operationName.replace(/_/g, '').toLowerCase();
            // Solo remover "Login" si no estamos en la operación login
            if (currentOperationClean !== 'login') {
                cleanType = cleanType.replace(/LoginResponse$/, ''); // CompanyLoginResponse -> CompanyResponse
                cleanType = cleanType.replace(/LoginRequest$/, ''); // CompanyLoginRequest -> CompanyRequest  
                cleanType = cleanType.replace(/Login$/, ''); // CompanyLogin -> Company
            }
            // Solo remover Response/Request si el tipo ya los incluye redundantemente
            if (type === 'response' && cleanType.endsWith('Response')) {
                // No hacer nada - mantener Response
            }
            else if (type === 'request' && cleanType.endsWith('Request')) {
                // No hacer nada - mantener Request
            }
            else {
                // Remover sufijos generales solo si no coinciden con el tipo actual
                cleanType = cleanType.replace(/Response$/, '');
                cleanType = cleanType.replace(/Request$/, '');
            }
            let typeKebab = cleanType.replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, '').replace(/[\[\]]/g, 'array');
            const fileSuffix = type === 'request' ? 'request' : 'response';
            // Detectar y evitar duplicación de operaciones en el nombre del tipo para archivos
            const operationInFileName = operationKebab.toLowerCase();
            if (typeKebab.includes(operationInFileName)) {
                // Eliminar la operación del tipo: user-login-response -> user-response
                typeKebab = typeKebab.replace(new RegExp(`-${operationInFileName}`, 'gi'), '');
            }
            // Si el tipo ya termina en request o response, no duplicar
            const needsSuffix = !field.type.toLowerCase().endsWith('response') && !field.type.toLowerCase().endsWith('request');
            const nestedFileName = needsSuffix
                ? `${serviceNameKebab}-${operationKebab}-${typeKebab}-${fileSuffix}-mapper.ts`
                : `${serviceNameKebab}-${operationKebab}-${typeKebab}-mapper.ts`;
            fs.writeFileSync(path.join(operationFolder, nestedFileName), nestedMapper);
            const formattedFieldType = toPascalCase(field.type);
            const formattedServiceName = toPascalCase(serviceName);
            const cleanOperationName = operationName
                .replace(/_/g, '-')
                .split('-')
                .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
                .join('');
            const classSuffix = type === 'request' ? 'Request' : 'Response';
            // Aplicar la misma lógica de limpieza contextual que en generateIndividualNestedMapper
            let finalFieldType = formattedFieldType;
            const currentOperationCleanForExport = operationName.replace(/_/g, '').toLowerCase();
            // Solo remover "Login" si no estamos en la operación login
            if (currentOperationCleanForExport !== 'login') {
                finalFieldType = finalFieldType.replace(/LoginResponse$/, ''); // CompanyLoginResponse -> CompanyResponse
                finalFieldType = finalFieldType.replace(/LoginRequest$/, ''); // CompanyLoginRequest -> CompanyRequest  
                finalFieldType = finalFieldType.replace(/Login$/, ''); // CompanyLogin -> Company
            }
            // Solo remover Response/Request si el tipo ya los incluye redundantemente
            if (type === 'response' && finalFieldType.endsWith('Response')) {
                // No hacer nada - mantener Response
            }
            else if (type === 'request' && finalFieldType.endsWith('Request')) {
                // No hacer nada - mantener Request
            }
            else {
                // Remover sufijos generales solo si no coinciden con el tipo actual
                finalFieldType = finalFieldType.replace(/Response$/, '');
                finalFieldType = finalFieldType.replace(/Request$/, '');
            }
            // Detectar y evitar duplicación de operaciones en el nombre del tipo
            const operationInTypeName = cleanOperationName.toLowerCase();
            const typeNameLower = finalFieldType.toLowerCase();
            // Si el tipo incluye el nombre de la operación, removerlo para evitar duplicación
            if (typeNameLower.includes(operationInTypeName)) {
                finalFieldType = finalFieldType.replace(new RegExp(cleanOperationName, 'gi'), '');
            }
            // Determinar si necesita sufijo después de la limpieza
            const finalNeedsSuffix = !finalFieldType.endsWith('Response') && !finalFieldType.endsWith('Request');
            // Generar nombre con patrón correcto: <Flujo><Proceso><Tipo><Request/Response>Mapper
            const mapperClassName = finalNeedsSuffix
                ? `${formattedServiceName}${cleanOperationName}${finalFieldType}${classSuffix}Mapper`
                : `${formattedServiceName}${cleanOperationName}${finalFieldType}Mapper`;
            exportStatements.push(`export { ${mapperClassName} } from './${operationName}/${nestedFileName.replace('.ts', '')}';`);
            // Procesar campos anidados recursivamente
            if (field.nestedFields && field.nestedFields.length > 0) {
                field.nestedFields.forEach(processNestedFields);
            }
        }
    }
    if (fields && fields.length > 0) {
        fields.forEach(processNestedFields);
    }
}
async function createDirectoriesIfNotExists(paths) {
    for (const [key, dir] of Object.entries(paths)) {
        if (typeof dir === 'string') {
            console.log(chalk_1.default.cyan(`📂 Directorio creado/verificado: ${dir}`));
            await fs.ensureDir(dir);
        }
    }
}
// Funciones auxiliares copiadas del generador original
function getDefaultFields() {
    return [
        { name: 'name', type: 'string', required: true },
        { name: 'state', type: 'boolean', required: false }
    ];
}
function toPascalCase(str) {
    return str
        .replace(/[\[\]]/g, 'Array') // ← Fix: Convert brackets to 'Array' before PascalCase conversion
        .replace(/(^|_|-)(\w)/g, (_, __, c) => c ? c.toUpperCase() : '')
        .replace(/Dto$/i, 'Entity')
        .replace(/EntityEntity$/, 'Entity');
}
function toScreamingSnakeCase(str) {
    return str.replace(/([A-Z])/g, '_$1').toUpperCase().replace(/^_/, '');
}
function toSnakeCase(str) {
    return str
        .replace(/([A-Z])/g, '_$1')
        .replace(/^_/, '')
        .toLowerCase();
}
function generateBusinessEntityInterface(serviceName, operation, type) {
    const operationName = getOperationNameFromPath(operation.path, type);
    const cleanOperationName = operationName
        .replace(/_/g, '-')
        .split('-')
        .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
        .join('');
    const interfaceName = `I${toPascalCase(serviceName)}${cleanOperationName}${type === 'request' ? 'Request' : 'Response'}Entity`;
    const fields = type === 'request' ? operation.fields : operation.responseFields;
    let imports = [];
    let content = '';
    if (fields && fields.length > 0) {
        fields.forEach((field) => {
            const optionalMark = field.required ? '' : '?';
            const arrayMark = field.isArray ? '[]' : '';
            // Mantener los nombres de campos tal como vienen del swagger (snake_case)
            const fieldName = field.name;
            let fieldType = field.type;
            if (fieldType && !['string', 'number', 'boolean', 'any', 'object', 'array'].includes(fieldType)) {
                const typeName = toPascalCase(fieldType);
                const suffix = type === 'request' ? 'Request' : 'Response';
                // Usar patrón completo: I<Flujo><Proceso><Tipo><Request/Response>Entity
                // Limpiar el tipo para obtener solo el nombre base sin sufijos
                let cleanTypeName = typeName;
                cleanTypeName = cleanTypeName.replace(/LoginResponse$/, '');
                cleanTypeName = cleanTypeName.replace(/LoginRequest$/, '');
                cleanTypeName = cleanTypeName.replace(/Login$/, '');
                cleanTypeName = cleanTypeName.replace(/Response$/, '');
                cleanTypeName = cleanTypeName.replace(/Request$/, '');
                // Limpiar corchetes para nombres de interfaces
                cleanTypeName = cleanTypeName.replace(/[\[\]]/g, 'Array');
                fieldType = `I${toPascalCase(serviceName)}${cleanOperationName}${cleanTypeName}${suffix}Entity`;
                // Generar nombre de archivo con patrón correcto: i-<flujo>-<proceso>-<tipo>-<request/response>-entity
                // Limpiar el tipo para obtener solo el nombre base sin sufijos Login/Response
                let cleanType = field.type;
                // Remover múltiples sufijos en orden específico
                cleanType = cleanType.replace(/LoginResponse$/, ''); // CompanyLoginResponse -> Company
                cleanType = cleanType.replace(/LoginRequest$/, ''); // CompanyLoginRequest -> Company  
                cleanType = cleanType.replace(/Login$/, ''); // CompanyLogin -> Company
                cleanType = cleanType.replace(/Response$/, ''); // CompanyResponse -> Company
                cleanType = cleanType.replace(/Request$/, ''); // CompanyRequest -> Company
                const baseFileName = cleanType.replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, '').replace(/[\[\]]/g, 'array');
                const serviceNameKebab = serviceName.toLowerCase().replace(/([A-Z])/g, '-$1').replace(/^-/, '');
                const operationKebab = operationName.replace(/_/g, '-');
                const importFileName = `i-${serviceNameKebab}-${operationKebab}-${baseFileName}-${type}-entity`;
                imports.push(`import { ${fieldType} } from "./${importFileName}";`);
            }
            content += `  ${fieldName}${optionalMark}: ${fieldType}${arrayMark};\n`;
        });
    }
    else {
        content += `  // Define los campos del ${type} aquí\n`;
    }
    const importsSection = imports.length > 0 ? imports.join('\n') + '\n\n' : '';
    return `${importsSection}export interface ${interfaceName} {
${content}}`;
}
function convertToCamelCase(str) {
    return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
}
function getFieldOptionalStatus(field, type) {
    if (type === 'save') {
        return !field.required;
    }
    if (type === 'update') {
        return true; // En update, todos los campos son opcionales excepto ID
    }
    return !field.required;
}
function getTypeScriptType(field) {
    switch (field.type) {
        case 'string':
            return 'string';
        case 'number':
        case 'integer':
            return 'number';
        case 'boolean':
            return 'boolean';
        case 'array':
            return 'any[]';
        case 'object':
            return 'any';
        default:
            return 'string';
    }
}
function generateBusinessMapper(serviceName, operation, type, apiName = 'platform') {
    const operationName = getOperationNameFromPath(operation.path, type);
    const cleanOperationName = operationName
        .replace(/_/g, '-')
        .split('-')
        .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
        .join('');
    const serviceNameLower = serviceName.toLowerCase();
    const dtoInterfaceName = `I${toPascalCase(serviceName)}${cleanOperationName}${type === 'request' ? 'Request' : 'Response'}DTO`;
    const entityInterfaceName = `I${toPascalCase(serviceName)}${cleanOperationName}${type === 'request' ? 'Request' : 'Response'}Entity`;
    const mapperClassName = `${toPascalCase(serviceName)}${cleanOperationName}${type === 'request' ? 'Request' : 'Response'}Mapper`;
    const fields = type === 'request' ? operation.fields : operation.responseFields;
    // Generar campos para mapFrom (Entity -> DTO)
    let mapFromFields = '';
    // Generar campos para mapTo (DTO -> Entity)  
    let mapToFields = '';
    // Agregar imports e instancias de mappers anidados
    let nestedMapperImports = '';
    let nestedMapperInstances = '';
    if (fields && fields.length > 0) {
        const nestedMappers = [];
        const nestedMapperReferences = {}; // Para evitar duplicados
        const mapFromMappings = fields.map((field) => {
            const dtoFieldName = convertToCamelCase(field.name); // DTO usa camelCase
            const entityFieldName = field.name; // Entity usa nombres del swagger (snake_case)
            // Si es un tipo complejo, usar mapper específico
            if (field.type && !['string', 'number', 'boolean', 'any', 'object', 'array'].includes(field.type)) {
                const formattedFieldType = toPascalCase(field.type);
                const suffix = type === 'request' ? 'Request' : 'Response';
                const mapperClassName = formattedFieldType.endsWith('Response') || formattedFieldType.endsWith('Request')
                    ? `${formattedFieldType}Mapper`
                    : `${formattedFieldType}${suffix}Mapper`;
                // Crear un nombre de variable único sin duplicaciones usando camelCase correcto
                const cleanFieldType = formattedFieldType.endsWith('Response') || formattedFieldType.endsWith('Request')
                    ? formattedFieldType.replace(/Response$|Request$/, '')
                    : formattedFieldType;
                const nestedMapperName = `${cleanFieldType.charAt(0).toLowerCase() + cleanFieldType.slice(1)}${suffix.charAt(0).toLowerCase() + suffix.slice(1)}Mapper`;
                // Generar nombre de método abreviado (sin prefijo de servicio y operación)
                const formattedServiceName = toPascalCase(serviceName);
                const methodName = mapperClassName.replace(new RegExp(`^${formattedServiceName}${cleanOperationName}`, ''), '');
                // Solo agregar si no existe ya
                if (!nestedMapperReferences[nestedMapperName]) {
                    nestedMapperReferences[nestedMapperName] = mapperClassName;
                    nestedMappers.push(`    private ${nestedMapperName} = Injection${toPascalCase(apiName)}Business${toPascalCase(serviceName)}${cleanOperationName}Mapper.${methodName}()`);
                }
                if (field.isArray) {
                    return `            ${dtoFieldName}: this.${nestedMapperName}.mapFromList(param.${entityFieldName} ?? [])`;
                }
                else {
                    return `            ${dtoFieldName}: this.${nestedMapperName}.mapFrom(param.${entityFieldName})`;
                }
            }
            else {
                return `            ${dtoFieldName}: param.${entityFieldName}`;
            }
        });
        const mapToMappings = fields.map((field) => {
            const dtoFieldName = convertToCamelCase(field.name); // DTO usa camelCase
            const entityFieldName = field.name; // Entity usa nombres del swagger (snake_case)
            // Si es un tipo complejo, usar mapper específico
            if (field.type && !['string', 'number', 'boolean', 'any', 'object', 'array'].includes(field.type)) {
                const formattedFieldType = toPascalCase(field.type);
                const suffix = type === 'request' ? 'Request' : 'Response';
                const cleanFieldType = formattedFieldType.endsWith('Response') || formattedFieldType.endsWith('Request')
                    ? formattedFieldType.replace(/Response$|Request$/, '')
                    : formattedFieldType;
                const nestedMapperName = `${cleanFieldType.charAt(0).toLowerCase() + cleanFieldType.slice(1)}${suffix.charAt(0).toLowerCase() + suffix.slice(1)}Mapper`;
                if (field.isArray) {
                    return `            ${entityFieldName}: this.${nestedMapperName}.mapToList(param.${dtoFieldName} ?? [])`;
                }
                else {
                    return `            ${entityFieldName}: this.${nestedMapperName}.mapTo(param.${dtoFieldName})`;
                }
            }
            else {
                return `            ${entityFieldName}: param.${dtoFieldName}`;
            }
        });
        // Agregar imports e instancias de mappers anidados si existen
        if (nestedMappers.length > 0) {
            nestedMapperImports = `import { Injection${toPascalCase(apiName)}Business${toPascalCase(serviceName)}${cleanOperationName}Mapper } from "@${apiName}/infrastructure/mappers/apis/${apiName}/injection/business/${serviceNameLower}/injection-${apiName}-business-${serviceNameLower}-${operationName.replace(/_/g, '-')}-mapper";\n`;
            nestedMapperInstances = nestedMappers.join('\n');
        }
        mapFromFields = mapFromMappings.join(',\n');
        mapToFields = mapToMappings.join(',\n');
    }
    return `import { Mapper } from "@core/classes";
import { ${dtoInterfaceName} } from "@${apiName}/domain/models/apis/${apiName}/business/${serviceNameLower}";
import { ${entityInterfaceName} } from "@${apiName}/infrastructure/entities/apis/${apiName}/business/${serviceNameLower}";
${nestedMapperImports}
export class ${mapperClassName} extends Mapper<${entityInterfaceName}, ${dtoInterfaceName}> {

    private static instance: ${mapperClassName};
${nestedMapperInstances}
    public constructor() { super(); }

    public static getInstance(): ${mapperClassName} {
        if (!${mapperClassName}.instance)
            ${mapperClassName}.instance = new ${mapperClassName}();
        return ${mapperClassName}.instance;
    }

    public mapFrom(param: ${entityInterfaceName}): ${dtoInterfaceName} {
        return {
${mapFromFields}
        }
    }

    public mapFromList(params: ${entityInterfaceName}[]): ${dtoInterfaceName}[] {
        return params.map((param: ${entityInterfaceName}) => {
            return this.mapFrom(param)
        })
    }

    public mapTo(param: ${dtoInterfaceName}): ${entityInterfaceName} {
        return {
${mapToFields}
        }
    }

    public mapToList(params: ${dtoInterfaceName}[]): ${entityInterfaceName}[] {
        return params.map((param: ${dtoInterfaceName}) => {
            return this.mapTo(param);
        })
    }
}`;
}
function generateIndividualNestedMapper(typeName, field, apiName, serviceName, operationName, type = 'response') {
    const serviceNameLower = serviceName.toLowerCase();
    const formattedTypeName = toPascalCase(typeName);
    const formattedServiceName = toPascalCase(serviceName);
    const suffix = type === 'request' ? 'Request' : 'Response';
    // Crear cleanOperationName aquí también
    const cleanOperationName = operationName
        .replace(/_/g, '-')
        .split('-')
        .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
        .join('');
    // Aplicar el patrón correcto: <Flujo><Proceso><Tipo><Request/Response><DTO/Entity/Mapper>
    // Determinar si necesita sufijo basado en el tipo original
    const needsSuffix = !formattedTypeName.endsWith('Response') && !formattedTypeName.endsWith('Request');
    // Limpiar el tipo para obtener solo el nombre base sin sufijos Login/Response 
    // pero preservando Response/Request cuando es la operación actual
    let cleanTypeName = formattedTypeName;
    const currentOperationClean = operationName.replace(/_/g, '').toLowerCase();
    // Solo remover "Login" si no estamos en la operación login
    if (currentOperationClean !== 'login') {
        cleanTypeName = cleanTypeName.replace(/LoginResponse$/, ''); // CompanyLoginResponse -> CompanyResponse
        cleanTypeName = cleanTypeName.replace(/LoginRequest$/, ''); // CompanyLoginRequest -> CompanyRequest  
        cleanTypeName = cleanTypeName.replace(/Login$/, ''); // CompanyLogin -> Company
    }
    // Solo remover Response/Request si el tipo ya los incluye redundantemente
    if (type === 'response' && cleanTypeName.endsWith('Response')) {
        // No hacer nada - mantener Response
    }
    else if (type === 'request' && cleanTypeName.endsWith('Request')) {
        // No hacer nada - mantener Request
    }
    else {
        // Remover sufijos generales solo si no coinciden con el tipo actual
        cleanTypeName = cleanTypeName.replace(/Response$/, '');
        cleanTypeName = cleanTypeName.replace(/Request$/, '');
    }
    // Limpiar corchetes para nombres de interfaces
    cleanTypeName = cleanTypeName.replace(/[\[\]]/g, 'Array');
    // Detectar y evitar duplicación de operaciones en el nombre del tipo
    // Ejemplo: UserRefreshToken -> User (cuando operationName es "refresh-token")
    const operationInTypeName = cleanOperationName.toLowerCase();
    const typeNameLower = cleanTypeName.toLowerCase();
    // Si el tipo incluye el nombre de la operación, removerlo para evitar duplicación
    if (typeNameLower.includes(operationInTypeName)) {
        // Eliminar la operación del tipo: UserRefreshToken -> User
        cleanTypeName = cleanTypeName.replace(new RegExp(cleanOperationName, 'gi'), '');
    }
    // Determinar si necesita sufijo después de la limpieza
    const finalNeedsSuffix = !cleanTypeName.endsWith('Response') && !cleanTypeName.endsWith('Request');
    const dtoInterfaceName = finalNeedsSuffix
        ? `I${formattedServiceName}${cleanOperationName}${cleanTypeName}${suffix}DTO`
        : `I${formattedServiceName}${cleanOperationName}${cleanTypeName}DTO`;
    const entityInterfaceName = finalNeedsSuffix
        ? `I${formattedServiceName}${cleanOperationName}${cleanTypeName}${suffix}Entity`
        : `I${formattedServiceName}${cleanOperationName}${cleanTypeName}Entity`;
    const mapperClassName = finalNeedsSuffix
        ? `${formattedServiceName}${cleanOperationName}${cleanTypeName}${suffix}Mapper`
        : `${formattedServiceName}${cleanOperationName}${cleanTypeName}Mapper`;
    let mapFromFields = '';
    let mapToFields = '';
    let nestedMapperImports = '';
    let nestedMapperInstances = '';
    if (field.nestedFields && field.nestedFields.length > 0) {
        const nestedMappers = [];
        const nestedMapperReferences = {}; // Para evitar duplicados
        const mapFromMappings = field.nestedFields.map((nestedField) => {
            const dtoFieldName = convertToCamelCase(nestedField.name);
            const entityFieldName = nestedField.name;
            if (nestedField.type && !['string', 'number', 'boolean', 'any', 'object', 'array'].includes(nestedField.type) && !nestedField.isEnum) {
                const nestedFieldTypeName = toPascalCase(nestedField.type);
                const nestedSuffix = type === 'request' ? 'Request' : 'Response';
                // Para mappers anidados, usar lógica simplificada más coherente con los archivos generados
                // Siempre remover sufijos duplicados y mantener solo el nombre base + sufijo correspondiente al tipo
                let cleanNestedFieldType = nestedFieldTypeName;
                // Remover sufijos redundantes para obtener nombre base limpio
                cleanNestedFieldType = cleanNestedFieldType.replace(/LoginResponse$|LoginRequest$|Response$|Request$/, '');
                // Generar nombre de clase de mapper basado en las clases reales que se generan
                const nestedMapperClassName = `${toPascalCase(serviceName)}${cleanOperationName}${cleanNestedFieldType}${nestedSuffix}Mapper`;
                // Crear un nombre de variable único sin duplicaciones usando camelCase correcto
                // Para la variable, extraer solo el nombre base sin sufijos y agregar ResponseMapper/RequestMapper
                let variableBaseName = nestedFieldTypeName;
                // Remover todos los sufijos para obtener el nombre base limpio
                variableBaseName = variableBaseName.replace(/LoginResponse$|LoginRequest$|Login$|Response$|Request$/, '');
                const nestedMapperName = `${variableBaseName.charAt(0).toLowerCase() + variableBaseName.slice(1)}${nestedSuffix.charAt(0).toLowerCase() + nestedSuffix.slice(1)}Mapper`;
                // Generar nombre de método abreviado (sin prefijo de servicio y operación)
                const formattedServiceName = toPascalCase(serviceName);
                const methodName = nestedMapperClassName.replace(new RegExp(`^${formattedServiceName}${cleanOperationName}`, ''), '');
                // Solo agregar si no existe ya
                if (!nestedMapperReferences[nestedMapperName]) {
                    nestedMapperReferences[nestedMapperName] = nestedMapperClassName;
                    nestedMappers.push(`    private ${nestedMapperName} = Injection${toPascalCase(apiName)}Business${toPascalCase(serviceName)}${cleanOperationName}Mapper.${methodName}()`);
                }
                if (nestedField.isArray) {
                    return `            ${dtoFieldName}: this.${nestedMapperName}.mapFromList(param.${entityFieldName} ?? [])`;
                }
                else {
                    return `            ${dtoFieldName}: this.${nestedMapperName}.mapFrom(param.${entityFieldName})`;
                }
            }
            else {
                return `            ${dtoFieldName}: param.${entityFieldName}`;
            }
        });
        const mapToMappings = field.nestedFields.map((nestedField) => {
            const dtoFieldName = convertToCamelCase(nestedField.name);
            const entityFieldName = nestedField.name;
            if (nestedField.type && !['string', 'number', 'boolean', 'any', 'object', 'array'].includes(nestedField.type) && !nestedField.isEnum) {
                // Usar el mismo nombre de variable que en mapFromMappings aplicando la misma lógica contextual
                const nestedFieldTypeName = toPascalCase(nestedField.type);
                const nestedSuffix = type === 'request' ? 'Request' : 'Response';
                // Para mappers anidados, usar la misma lógica simplificada
                let cleanNestedFieldType = nestedFieldTypeName;
                // Remover sufijos redundantes para obtener nombre base limpio
                cleanNestedFieldType = cleanNestedFieldType.replace(/LoginResponse$|LoginRequest$|Response$|Request$/, '');
                // Para la variable, extraer solo el nombre base sin sufijos y agregar ResponseMapper/RequestMapper
                let variableBaseName = nestedFieldTypeName;
                // Remover todos los sufijos para obtener el nombre base limpio
                variableBaseName = variableBaseName.replace(/LoginResponse$|LoginRequest$|Login$|Response$|Request$/, '');
                const nestedMapperName = `${variableBaseName.charAt(0).toLowerCase() + variableBaseName.slice(1)}${nestedSuffix.charAt(0).toLowerCase() + nestedSuffix.slice(1)}Mapper`;
                if (nestedField.isArray) {
                    return `            ${entityFieldName}: this.${nestedMapperName}.mapToList(param.${dtoFieldName} ?? [])`;
                }
                else {
                    return `            ${entityFieldName}: this.${nestedMapperName}.mapTo(param.${dtoFieldName})`;
                }
            }
            else {
                return `            ${entityFieldName}: param.${dtoFieldName}`;
            }
        });
        if (nestedMappers.length > 0) {
            nestedMapperImports = `import { Injection${toPascalCase(apiName)}Business${toPascalCase(serviceName)}${cleanOperationName}Mapper } from "@${apiName}/infrastructure/mappers/apis/${apiName}/injection/business/${serviceNameLower}/injection-${apiName}-business-${serviceNameLower}-${operationName.replace(/_/g, '-')}-mapper";\n`;
            nestedMapperInstances = nestedMappers.join('\n');
        }
        mapFromFields = mapFromMappings.join(',\n');
        mapToFields = mapToMappings.join(',\n');
    }
    return `import { Mapper } from "@core/classes";
import { ${dtoInterfaceName} } from "@${apiName}/domain/models/apis/${apiName}/business/${serviceNameLower}";
import { ${entityInterfaceName} } from "@${apiName}/infrastructure/entities/apis/${apiName}/business/${serviceNameLower}";
${nestedMapperImports}
export class ${mapperClassName} extends Mapper<${entityInterfaceName}, ${dtoInterfaceName}> {

    private static instance: ${mapperClassName};
${nestedMapperInstances}
    public constructor() { super(); }

    public static getInstance(): ${mapperClassName} {
        if (!${mapperClassName}.instance)
            ${mapperClassName}.instance = new ${mapperClassName}();
        return ${mapperClassName}.instance;
    }

    public mapFrom(param: ${entityInterfaceName}): ${dtoInterfaceName} {
        return {
${mapFromFields}
        }
    }

    public mapFromList(params: ${entityInterfaceName}[]): ${dtoInterfaceName}[] {
        return params.map((param: ${entityInterfaceName}) => {
            return this.mapFrom(param)
        })
    }

    public mapTo(param: ${dtoInterfaceName}): ${entityInterfaceName} {
        return {
${mapToFields}
        }
    }

    public mapToList(params: ${dtoInterfaceName}[]): ${entityInterfaceName}[] {
        return params.map((param: ${dtoInterfaceName}) => {
            return this.mapTo(param);
        })
    }
}`;
}
async function generateNestedDTOsForOperation(serviceName, operation, type, operationFolder, apiName, exportStatements, operationName) {
    const fields = type === 'request' ? operation.fields : operation.responseFields;
    const generated = new Set();
    function processNestedFields(field) {
        if (field.type && !['string', 'number', 'boolean', 'any', 'object', 'array'].includes(field.type) && !generated.has(field.type)) {
            generated.add(field.type);
            let nestedDTO;
            // Si es un enum, generar archivo de enum en lugar de interfaz
            if (field.isEnum && field.enumValues) {
                nestedDTO = generateIndividualEnum(field.type, field, apiName, serviceName, operationName, type);
            }
            else {
                // Generar DTO individual para cada interface anidada con patrón correcto
                nestedDTO = generateIndividualNestedDTO(field.type, field, apiName, serviceName, operationName, type);
            }
            // Patrón correcto: i-<flujo>-<proceso>-<tipo>-<request/response>-dto.ts
            // Limpiar el tipo para obtener solo el nombre base sin sufijos Login/Response
            let cleanType = field.type;
            // Remover múltiples sufijos en orden específico
            cleanType = cleanType.replace(/LoginResponse$/, ''); // CompanyLoginResponse -> Company
            cleanType = cleanType.replace(/LoginRequest$/, ''); // CompanyLoginRequest -> Company  
            cleanType = cleanType.replace(/Login$/, ''); // CompanyLogin -> Company
            cleanType = cleanType.replace(/Response$/, ''); // CompanyResponse -> Company
            cleanType = cleanType.replace(/Request$/, ''); // CompanyRequest -> Company
            const baseFileName = cleanType.replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, '').replace(/[\[\]]/g, 'array');
            const serviceNameKebab = serviceName.toLowerCase().replace(/([A-Z])/g, '-$1').replace(/^-/, '');
            const operationKebab = operationName.replace(/_/g, '-');
            let nestedFileName;
            if (field.isEnum && field.enumValues) {
                // Para enums, usar el patrón: <flujo>-<proceso>-<tipo>-<request/response>-dto.ts
                nestedFileName = `${serviceNameKebab}-${operationKebab}-${baseFileName}-${type}-dto.ts`;
            }
            else {
                // Para interfaces, usar el patrón normal
                nestedFileName = `i-${serviceNameKebab}-${operationKebab}-${baseFileName}-${type}-dto.ts`;
            }
            fs.writeFileSync(path.join(operationFolder, nestedFileName), nestedDTO);
            // Generar nombre de clase que coincida con el patrón del archivo
            const cleanOperationName = operationName
                .replace(/_/g, '-')
                .split('-')
                .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
                .join('');
            // Limpiar el tipo para obtener solo el nombre base
            let cleanFieldType = field.type;
            cleanFieldType = cleanFieldType.replace(/LoginResponse$/, '');
            cleanFieldType = cleanFieldType.replace(/LoginRequest$/, '');
            cleanFieldType = cleanFieldType.replace(/Login$/, '');
            cleanFieldType = cleanFieldType.replace(/Response$/, '');
            cleanFieldType = cleanFieldType.replace(/Request$/, '');
            const formattedFieldType = toPascalCase(cleanFieldType);
            let exportClassName;
            if (field.isEnum && field.enumValues) {
                // Para enums, usar el nombre del enum en SCREAMING_SNAKE_CASE (evitar duplicar "Enum")
                const enumSuffix = formattedFieldType.endsWith('Enum') ? '' : 'Enum';
                const pascalCaseName = `${toPascalCase(serviceName)}${cleanOperationName}${formattedFieldType}${enumSuffix}`;
                exportClassName = toScreamingSnakeCase(pascalCaseName);
            }
            else {
                // Para interfaces, usar el patrón normal
                const suffix = type === 'request' ? 'Request' : 'Response';
                exportClassName = `I${toPascalCase(serviceName)}${cleanOperationName}${formattedFieldType}${suffix}DTO`;
            }
            // Export statement con el nombre correcto del archivo
            const fileNameForExport = nestedFileName.replace('.ts', '');
            exportStatements.push(`export { ${exportClassName} } from './${operationName}/${fileNameForExport}';`);
            // Procesar campos anidados recursivamente
            if (field.nestedFields && field.nestedFields.length > 0) {
                field.nestedFields.forEach(processNestedFields);
            }
        }
    }
    if (fields && fields.length > 0) {
        fields.forEach(processNestedFields);
    }
}
function generateBusinessDTO(serviceName, operation, type, apiName = 'platform') {
    const operationName = getOperationNameFromPath(operation.path, type);
    const cleanOperationName = operationName
        .replace(/_/g, '-')
        .split('-')
        .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
        .join('');
    const dtoInterfaceName = `I${toPascalCase(serviceName)}${cleanOperationName}${type === 'request' ? 'Request' : 'Response'}DTO`;
    const fields = type === 'request' ? operation.fields : operation.responseFields;
    let dtoFields = '';
    let imports = [];
    if (fields && fields.length > 0) {
        const fieldMappings = fields.map((field) => {
            const fieldName = convertToCamelCase(field.name); // DTO usa camelCase
            const optionalMark = field.required ? '' : '?';
            const arrayMark = field.isArray ? '[]' : '';
            // Si es un tipo complejo, usar el tipo con el sufijo DTO
            if (field.type && !['string', 'number', 'boolean', 'any', 'object', 'array'].includes(field.type)) {
                const typeName = toPascalCase(field.type);
                const suffix = type === 'request' ? 'Request' : 'Response';
                // Usar patrón completo: I<Flujo><Proceso><Tipo><Request/Response>DTO
                // Limpiar el tipo para obtener solo el nombre base sin sufijos
                let cleanTypeName = typeName;
                cleanTypeName = cleanTypeName.replace(/LoginResponse$/, '');
                cleanTypeName = cleanTypeName.replace(/LoginRequest$/, '');
                cleanTypeName = cleanTypeName.replace(/Login$/, '');
                cleanTypeName = cleanTypeName.replace(/Response$/, '');
                cleanTypeName = cleanTypeName.replace(/Request$/, '');
                // Limpiar corchetes para nombres de interfaces
                cleanTypeName = cleanTypeName.replace(/[\[\]]/g, 'Array');
                const fieldType = `I${toPascalCase(serviceName)}${cleanOperationName}${cleanTypeName}${suffix}DTO`;
                // Generar nombre de archivo con patrón correcto: i-<flujo>-<proceso>-<tipo>-<request/response>-dto
                // Limpiar el tipo para obtener solo el nombre base sin sufijos Login/Response
                let cleanType = field.type;
                // Remover múltiples sufijos en orden específico
                cleanType = cleanType.replace(/LoginResponse$/, ''); // CompanyLoginResponse -> Company
                cleanType = cleanType.replace(/LoginRequest$/, ''); // CompanyLoginRequest -> Company  
                cleanType = cleanType.replace(/Login$/, ''); // CompanyLogin -> Company
                cleanType = cleanType.replace(/Response$/, ''); // CompanyResponse -> Company
                cleanType = cleanType.replace(/Request$/, ''); // CompanyRequest -> Company
                const baseFileName = cleanType.replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, '').replace(/[\[\]]/g, 'array');
                const serviceNameKebab = serviceName.toLowerCase().replace(/([A-Z])/g, '-$1').replace(/^-/, '');
                const operationKebab = operationName.replace(/_/g, '-');
                const importFileName = `i-${serviceNameKebab}-${operationKebab}-${baseFileName}-${type}-dto`;
                imports.push(`import { ${fieldType} } from "./${importFileName}";`);
                return `  ${fieldName}${optionalMark}: ${fieldType}${arrayMark};`;
            }
            else {
                return `  ${fieldName}${optionalMark}: ${field.type}${arrayMark};`;
            }
        });
        dtoFields = fieldMappings.join('\n');
    }
    const importsSection = imports.length > 0 ? imports.join('\n') + '\n\n' : '';
    return `${importsSection}export interface ${dtoInterfaceName} {
${dtoFields}
}`;
}
function generateIndividualEnum(typeName, field, apiName, serviceName, operationName, type = 'response') {
    let cleanType = typeName;
    // Limpiar el tipo para obtener el nombre base
    cleanType = cleanType.replace(/LoginResponse$/, '');
    cleanType = cleanType.replace(/LoginRequest$/, '');
    cleanType = cleanType.replace(/Login$/, '');
    cleanType = cleanType.replace(/Response$/, '');
    cleanType = cleanType.replace(/Request$/, '');
    const formattedTypeName = toPascalCase(cleanType);
    const cleanOperationName = operationName
        .replace(/_/g, '-')
        .split('-')
        .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
        .join('');
    // Si el formattedTypeName ya contiene "Enum", no agregarlo de nuevo
    const enumSuffix = formattedTypeName.endsWith('Enum') ? '' : 'Enum';
    const pascalCaseName = `${toPascalCase(serviceName)}${cleanOperationName}${formattedTypeName}${enumSuffix}`;
    // Convertir a SCREAMING_SNAKE_CASE para el nombre del enum
    const enumName = toScreamingSnakeCase(pascalCaseName);
    // Generar valores del enum
    const enumValues = field.enumValues.map((value) => {
        // Convertir valores a nombres válidos de enum
        let enumKey = value
            .replace(/[^a-zA-Z0-9]/g, '_') // Reemplazar caracteres especiales con _
            .replace(/^_+|_+$/g, '') // Quitar _ al inicio y final
            .replace(/_+/g, '_'); // Consolidar múltiples _
        // Si está vacío después de limpiar, usar el valor original como clave
        if (!enumKey) {
            enumKey = `VALUE_${Math.abs(value.split('').reduce((a, b) => a + b.charCodeAt(0), 0))}`;
        }
        // Si empieza con número, agregar prefijo
        if (/^\d/.test(enumKey)) {
            enumKey = `VALUE_${enumKey}`;
        }
        // Casos especiales para operadores (nombres del backend)
        const operatorMappings = {
            '==': 'EQUALS',
            '>': 'GREATER_THAN',
            '<': 'LESS_THAN',
            '>=': 'GREATER_THAN_OR_EQUAL_TO',
            '<=': 'LESS_THAN_OR_EQUAL_TO',
            '!=': 'DIFFERENT_THAN',
            'like': 'LIKE',
            'in': 'IN',
            'between': 'BETWEEN'
        };
        if (operatorMappings[value]) {
            enumKey = operatorMappings[value];
        }
        else {
            // Convertir a UPPER_CASE si no es un operador especial
            enumKey = enumKey.toUpperCase();
        }
        return `  ${enumKey} = "${value}"`;
    }).join(',\n');
    return `export enum ${enumName} {
${enumValues}
}`;
}
function generateIndividualNestedDTO(typeName, field, apiName, serviceName, operationName, type = 'response') {
    // Generar nombre de interface que coincida con el patrón del archivo
    // i-<flujo>-<proceso>-<tipo>-<request/response>-dto.ts → I<Flujo><Proceso><Tipo><Request/Response>DTO
    const cleanOperationName = operationName
        .replace(/_/g, '-')
        .split('-')
        .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
        .join('');
    // Limpiar el tipo para obtener solo el nombre base
    let cleanType = typeName;
    cleanType = cleanType.replace(/LoginResponse$/, '');
    cleanType = cleanType.replace(/LoginRequest$/, '');
    cleanType = cleanType.replace(/Login$/, '');
    cleanType = cleanType.replace(/Response$/, '');
    cleanType = cleanType.replace(/Request$/, '');
    const formattedTypeName = toPascalCase(cleanType);
    const suffix = type === 'request' ? 'Request' : 'Response';
    const dtoInterfaceName = `I${toPascalCase(serviceName)}${cleanOperationName}${formattedTypeName}${suffix}DTO`;
    let dtoFields = '';
    let imports = [];
    if (field.nestedFields && field.nestedFields.length > 0) {
        const fieldMappings = field.nestedFields.map((nestedField) => {
            const fieldName = convertToCamelCase(nestedField.name); // DTO usa camelCase
            const optionalMark = nestedField.required ? '' : '?';
            const arrayMark = nestedField.isArray ? '[]' : '';
            // Si es un tipo complejo, usar el tipo con el sufijo DTO o Enum
            if (nestedField.type && !['string', 'number', 'boolean', 'any', 'object', 'array'].includes(nestedField.type)) {
                // Usar el patrón completo: I<Flujo><Proceso><Tipo><Request/Response>DTO o <Flujo><Proceso><Tipo>Enum
                let cleanNestedType = nestedField.type;
                cleanNestedType = cleanNestedType.replace(/LoginResponse$/, '');
                cleanNestedType = cleanNestedType.replace(/LoginRequest$/, '');
                cleanNestedType = cleanNestedType.replace(/Login$/, '');
                cleanNestedType = cleanNestedType.replace(/Response$/, '');
                cleanNestedType = cleanNestedType.replace(/Request$/, '');
                const formattedNestedTypeName = toPascalCase(cleanNestedType);
                let fieldType;
                if (nestedField.isEnum && nestedField.enumValues) {
                    // Para enums, usar el nombre del enum en SCREAMING_SNAKE_CASE (evitar duplicar "Enum")
                    const enumSuffix = formattedNestedTypeName.endsWith('Enum') ? '' : 'Enum';
                    const pascalCaseName = `${toPascalCase(serviceName)}${cleanOperationName}${formattedNestedTypeName}${enumSuffix}`;
                    fieldType = toScreamingSnakeCase(pascalCaseName);
                }
                else {
                    // Para interfaces, usar el patrón normal
                    const nestedSuffix = type === 'request' ? 'Request' : 'Response';
                    fieldType = `I${toPascalCase(serviceName)}${cleanOperationName}${formattedNestedTypeName}${nestedSuffix}DTO`;
                }
                // Agregar import para tipos complejos con patrón completo
                const formattedNestedType = cleanNestedType.replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, '');
                const serviceNameKebab = serviceName.toLowerCase().replace(/([A-Z])/g, '-$1').replace(/^-/, '');
                const operationNameKebab = operationName.replace(/_/g, '-');
                let importFileName;
                if (nestedField.isEnum && nestedField.enumValues) {
                    // Para enums, usar el patrón: <flujo>-<proceso>-<tipo>-<request/response>-dto
                    const typeKebab = type === 'request' ? 'request' : 'response';
                    importFileName = `${serviceNameKebab}-${operationNameKebab}-${formattedNestedType}-${typeKebab}-dto`;
                }
                else {
                    // Para interfaces, usar el patrón: i-<flujo>-<proceso>-<tipo>-<request/response>-dto
                    const typeKebab = type === 'request' ? 'request' : 'response';
                    importFileName = `i-${serviceNameKebab}-${operationNameKebab}-${formattedNestedType}-${typeKebab}-dto`;
                }
                imports.push(`import { ${fieldType} } from "./${importFileName}";`);
                return `  ${fieldName}${optionalMark}: ${fieldType}${arrayMark};`;
            }
            else {
                return `  ${fieldName}${optionalMark}: ${nestedField.type}${arrayMark};`;
            }
        });
        dtoFields = fieldMappings.join('\n');
    }
    const importsSection = imports.length > 0 ? imports.join('\n') + '\n\n' : '';
    return `${importsSection}export interface ${dtoInterfaceName} {
${dtoFields}
}`;
}
async function generateNestedEntitiesForOperation(serviceName, operation, type, operationFolder, apiName, exportStatements, operationName) {
    const fields = type === 'request' ? operation.fields : operation.responseFields;
    const generated = new Set();
    function processNestedFields(field) {
        if (field.type && !['string', 'number', 'boolean', 'any', 'object', 'array'].includes(field.type) && !generated.has(field.type)) {
            generated.add(field.type);
            let nestedEntity;
            // Si es un enum, generar archivo de enum en lugar de interfaz
            if (field.isEnum && field.enumValues) {
                nestedEntity = generateIndividualEnum(field.type, field, apiName, serviceName, operationName, type);
            }
            else {
                // Generar Entity individual para cada interface anidada con patrón correcto
                nestedEntity = generateIndividualNestedEntity(field.type, field, apiName, serviceName, operationName, type);
            }
            // Patrón correcto: i-<flujo>-<proceso>-<tipo>-<request/response>-entity.ts
            // Limpiar el tipo para obtener solo el nombre base sin sufijos Login/Response
            let cleanType = field.type;
            // Remover múltiples sufijos en orden específico
            cleanType = cleanType.replace(/LoginResponse$/, ''); // CompanyLoginResponse -> Company
            cleanType = cleanType.replace(/LoginRequest$/, ''); // CompanyLoginRequest -> Company  
            cleanType = cleanType.replace(/Login$/, ''); // CompanyLogin -> Company
            cleanType = cleanType.replace(/Response$/, ''); // CompanyResponse -> Company
            cleanType = cleanType.replace(/Request$/, ''); // CompanyRequest -> Company
            const baseFileName = cleanType.replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, '').replace(/[\[\]]/g, 'array');
            const serviceNameKebab = serviceName.toLowerCase().replace(/([A-Z])/g, '-$1').replace(/^-/, '');
            const operationKebab = operationName.replace(/_/g, '-');
            let nestedFileName;
            if (field.isEnum && field.enumValues) {
                // Para enums, usar el patrón: <flujo>-<proceso>-<tipo>-<request/response>-entity.ts
                nestedFileName = `${serviceNameKebab}-${operationKebab}-${baseFileName}-${type}-entity.ts`;
            }
            else {
                // Para interfaces, usar el patrón normal
                nestedFileName = `i-${serviceNameKebab}-${operationKebab}-${baseFileName}-${type}-entity.ts`;
            }
            fs.writeFileSync(path.join(operationFolder, nestedFileName), nestedEntity);
            // Generar nombre de clase que coincida con el patrón del archivo
            const cleanOperationName = operationName
                .replace(/_/g, '-')
                .split('-')
                .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
                .join('');
            // Limpiar el tipo para obtener solo el nombre base
            let cleanFieldType = field.type;
            cleanFieldType = cleanFieldType.replace(/LoginResponse$/, '');
            cleanFieldType = cleanFieldType.replace(/LoginRequest$/, '');
            cleanFieldType = cleanFieldType.replace(/Login$/, '');
            cleanFieldType = cleanFieldType.replace(/Response$/, '');
            cleanFieldType = cleanFieldType.replace(/Request$/, '');
            const formattedFieldType = toPascalCase(cleanFieldType);
            let exportClassName;
            if (field.isEnum && field.enumValues) {
                // Para enums, usar el nombre del enum en SCREAMING_SNAKE_CASE
                const enumSuffix = formattedFieldType.endsWith('Enum') ? '' : 'Enum';
                const pascalCaseName = `${toPascalCase(serviceName)}${cleanOperationName}${formattedFieldType}${enumSuffix}`;
                exportClassName = toScreamingSnakeCase(pascalCaseName);
            }
            else {
                // Para interfaces, usar el patrón normal
                const suffix = type === 'request' ? 'Request' : 'Response';
                exportClassName = `I${toPascalCase(serviceName)}${cleanOperationName}${formattedFieldType}${suffix}Entity`;
            }
            // Export statement con el nombre correcto del archivo
            const fileNameForExport = nestedFileName.replace('.ts', '');
            exportStatements.push(`export { ${exportClassName} } from './${operationName}/${fileNameForExport}';`);
            // Procesar campos anidados recursivamente
            if (field.nestedFields && field.nestedFields.length > 0) {
                field.nestedFields.forEach(processNestedFields);
            }
        }
    }
    if (fields && fields.length > 0) {
        fields.forEach(processNestedFields);
    }
}
function generateIndividualNestedEntity(typeName, field, apiName, serviceName, operationName, type = 'response') {
    // Generar nombre de interface que coincida con el patrón del archivo
    // i-<flujo>-<proceso>-<tipo>-<request/response>-entity.ts → I<Flujo><Proceso><Tipo><Request/Response>Entity
    const cleanOperationName = operationName
        .replace(/_/g, '-')
        .split('-')
        .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
        .join('');
    // Limpiar el tipo para obtener solo el nombre base
    let cleanType = typeName;
    cleanType = cleanType.replace(/LoginResponse$/, '');
    cleanType = cleanType.replace(/LoginRequest$/, '');
    cleanType = cleanType.replace(/Login$/, '');
    cleanType = cleanType.replace(/Response$/, '');
    cleanType = cleanType.replace(/Request$/, '');
    const formattedTypeName = toPascalCase(cleanType);
    const suffix = type === 'request' ? 'Request' : 'Response';
    const entityInterfaceName = `I${toPascalCase(serviceName)}${cleanOperationName}${formattedTypeName}${suffix}Entity`;
    let entityFields = '';
    let imports = [];
    if (field.nestedFields && field.nestedFields.length > 0) {
        const fieldMappings = field.nestedFields.map((nestedField) => {
            const fieldName = nestedField.name; // Entity usa snake_case como viene del swagger
            const optionalMark = nestedField.required ? '' : '?';
            const arrayMark = nestedField.isArray ? '[]' : '';
            // Si es un tipo complejo, usar el tipo con el sufijo Entity o Enum
            if (nestedField.type && !['string', 'number', 'boolean', 'any', 'object', 'array'].includes(nestedField.type)) {
                // Usar el patrón completo: I<Flujo><Proceso><Tipo><Request/Response>Entity o <Flujo><Proceso><Tipo>Enum
                let cleanNestedType = nestedField.type;
                cleanNestedType = cleanNestedType.replace(/LoginResponse$/, '');
                cleanNestedType = cleanNestedType.replace(/LoginRequest$/, '');
                cleanNestedType = cleanNestedType.replace(/Login$/, '');
                cleanNestedType = cleanNestedType.replace(/Response$/, '');
                cleanNestedType = cleanNestedType.replace(/Request$/, '');
                const formattedNestedTypeName = toPascalCase(cleanNestedType);
                let fieldType, importFileName;
                if (nestedField.isEnum && nestedField.enumValues) {
                    // Para enums, usar el nombre del enum en SCREAMING_SNAKE_CASE
                    const enumSuffix = formattedNestedTypeName.endsWith('Enum') ? '' : 'Enum';
                    const pascalCaseName = `${toPascalCase(serviceName)}${cleanOperationName}${formattedNestedTypeName}${enumSuffix}`;
                    fieldType = toScreamingSnakeCase(pascalCaseName);
                    // Para enums, usar el patrón: <flujo>-<proceso>-<tipo>-<request/response>-entity
                    const formattedNestedType = cleanNestedType.replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, '');
                    const serviceNameKebab = serviceName.toLowerCase().replace(/([A-Z])/g, '-$1').replace(/^-/, '');
                    const operationNameKebab = operationName.replace(/_/g, '-');
                    const typeKebab = type === 'request' ? 'request' : 'response';
                    importFileName = `${serviceNameKebab}-${operationNameKebab}-${formattedNestedType}-${typeKebab}-entity`;
                }
                else {
                    // Para interfaces, usar el patrón normal
                    const nestedSuffix = type === 'request' ? 'Request' : 'Response';
                    fieldType = `I${toPascalCase(serviceName)}${cleanOperationName}${formattedNestedTypeName}${nestedSuffix}Entity`;
                    // Patron: i-<flujo>-<proceso>-<tipo>-<request/response>-entity.ts        
                    const formattedNestedType = cleanNestedType.replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, '');
                    const serviceNameKebab = serviceName.toLowerCase().replace(/([A-Z])/g, '-$1').replace(/^-/, '');
                    const operationNameKebab = operationName.replace(/_/g, '-');
                    const typeKebab = type === 'request' ? 'request' : 'response';
                    importFileName = `i-${serviceNameKebab}-${operationNameKebab}-${formattedNestedType}-${typeKebab}-entity`;
                }
                imports.push(`import { ${fieldType} } from "./${importFileName}";`);
                return `  ${fieldName}${optionalMark}: ${fieldType}${arrayMark};`;
            }
            else {
                return `  ${fieldName}${optionalMark}: ${nestedField.type}${arrayMark};`;
            }
        });
        entityFields = fieldMappings.join('\n');
    }
    const importsSection = imports.length > 0 ? imports.join('\n') + '\n\n' : '';
    return `${importsSection}export interface ${entityInterfaceName} {
${entityFields}
}`;
}
// ======================================================================
// NUEVAS FUNCIONES PARA COMPLETAR EL FLUJO DE NEGOCIO
// ======================================================================
async function generateDomainRepositoryInterfaces(serviceName, paths, schema, apiName = 'platform') {
    if (schema?.businessOperations && schema.businessOperations.length > 0) {
        const serviceNameLower = serviceName.toLowerCase();
        const serviceNameKebab = serviceName.replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, '');
        const repositoryFilePath = path.join(paths.domainRepositories, `i-${serviceNameKebab}-repository.ts`);
        // Recopilar DTOs y Entities necesarias para las nuevas operaciones
        const newDtos = new Set();
        const newEntities = new Set();
        const newMethods = [];
        // Generar nuevos métodos abstractos
        for (const operation of schema.businessOperations) {
            const rawOperationName = getOperationNameFromPath(operation.path, operation.operationId.toLowerCase());
            const operationName = rawOperationName.replace(/_/g, '-');
            const cleanOperationName = operationName
                .replace(/_/g, '-')
                .split('-')
                .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
                .join('');
            // Convertir operationName a camelCase para el nombre del método
            const operationCamelCase = operationName
                .replace(/_/g, '-')
                .split('-')
                .map((word, index) => index === 0 ? word : word.charAt(0).toUpperCase() + word.slice(1))
                .join('');
            const baseResponseType = operation.responseFields && operation.responseFields.length > 0
                ? `I${toPascalCase(serviceName)}${cleanOperationName}ResponseDTO`
                : 'any';
            const responseType = operation.isResponseArray
                ? `${baseResponseType}[] | null`
                : `${baseResponseType} | null`;
            // Si tiene campos de request, incluir params, sino solo config
            const hasRequestFields = operation.fields && operation.fields.length > 0;
            const requestType = hasRequestFields ? `I${toPascalCase(serviceName)}${cleanOperationName}RequestEntity` : null;
            const params = hasRequestFields ? `params: ${requestType}, ` : '';
            // Agregar a los imports
            if (operation.responseFields && operation.responseFields.length > 0) {
                newDtos.add(`I${toPascalCase(serviceName)}${cleanOperationName}ResponseDTO`);
            }
            if (hasRequestFields) {
                newEntities.add(`I${toPascalCase(serviceName)}${cleanOperationName}RequestEntity`);
            }
            newMethods.push(`  abstract ${operationCamelCase}(${params}config: IConfigDTO): Promise<${responseType}>;`);
        }
        // Leer archivo existente si existe
        let existingMethods = [];
        let existingDtos = new Set();
        let existingEntities = new Set();
        if (await fs.pathExists(repositoryFilePath)) {
            try {
                const existingContent = await fs.readFile(repositoryFilePath, 'utf-8');
                // Extraer métodos abstractos existentes
                const newMethodNames = new Set(newMethods.map(m => {
                    const match = m.match(/abstract (\w+)\(/);
                    return match ? match[1] : '';
                }));
                // Extraer métodos usando regex
                const methodRegex = /abstract\s+(\w+)\([^)]*\):\s*Promise<[^>]+>;/g;
                const methodMatches = existingContent.matchAll(methodRegex);
                for (const match of methodMatches) {
                    const methodName = match[1];
                    if (!newMethodNames.has(methodName)) {
                        existingMethods.push(`  ${match[0]}`);
                    }
                }
                // Extraer DTOs existentes del primer import
                const dtoImportMatch = existingContent.match(/import \{([^}]+)\} from "@\w+\/domain\/models\/apis\/\w+\/business\/\w+";/);
                if (dtoImportMatch) {
                    dtoImportMatch[1].split(',').forEach(dto => {
                        const trimmed = dto.trim();
                        if (trimmed && !newDtos.has(trimmed)) {
                            existingDtos.add(trimmed);
                        }
                    });
                }
                // Extraer Entities existentes
                const entityImportMatch = existingContent.match(/import \{([^}]+)\} from "@\w+\/infrastructure\/entities\/apis\/\w+\/business\/\w+";/);
                if (entityImportMatch) {
                    entityImportMatch[1].split(',').forEach(entity => {
                        const trimmed = entity.trim();
                        if (trimmed && !newEntities.has(trimmed)) {
                            existingEntities.add(trimmed);
                        }
                    });
                }
                console.log(chalk_1.default.blue(`ℹ️  Archivo existente detectado - Agregando ${newMethods.length} método(s) nuevo(s), manteniendo ${existingMethods.length} método(s) existente(s)`));
            }
            catch (error) {
                console.log(chalk_1.default.yellow(`⚠️  No se pudo leer el archivo existente: ${error}`));
            }
        }
        // Combinar DTOs, Entities y Métodos
        const allDtos = [...Array.from(existingDtos), ...Array.from(newDtos)];
        const allEntities = [...Array.from(existingEntities), ...Array.from(newEntities)];
        const allMethods = [...existingMethods, ...newMethods];
        // Generar imports
        const dtoImport = allDtos.length > 0
            ? `import { 
  ${allDtos.join(',\n  ')}
} from "@${apiName}/domain/models/apis/${apiName}/business/${serviceNameLower}";`
            : '';
        const entityImport = allEntities.length > 0
            ? `import {
  ${allEntities.join(',\n  ')}
} from "@${apiName}/infrastructure/entities/apis/${apiName}/business/${serviceNameLower}";`
            : '';
        // Generar el archivo completo
        const repositoryInterface = `import { IConfigDTO } from "@core/interfaces/i-config-repository-dto";
${dtoImport}
${entityImport}

export abstract class I${toPascalCase(serviceName)}Repository {
${allMethods.join('\n')}
}`;
        await fs.writeFile(repositoryFilePath, repositoryInterface);
        console.log(chalk_1.default.green(`✅ Repository Interface: i-${serviceNameKebab}-repository.ts ${existingMethods.length > 0 ? '(actualizado)' : ''}`));
    }
    else {
        console.log(chalk_1.default.yellow('⚠️  No se generó repository interface porque no hay operaciones de negocio detectadas.'));
    }
}
async function generateDomainUseCases(serviceName, paths, schema, apiName = 'platform') {
    if (schema?.businessOperations && schema.businessOperations.length > 0) {
        const serviceNameLower = serviceName.toLowerCase();
        const serviceNameKebab = serviceName.replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, '');
        for (const operation of schema.businessOperations) {
            const rawOperationName = getOperationNameFromPath(operation.path, operation.operationId.toLowerCase());
            const operationName = rawOperationName.replace(/_/g, '-');
            const operationKebab = operationName.replace(/_/g, '-');
            const cleanOperationName = operationName
                .replace(/_/g, '-')
                .split('-')
                .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
                .join('');
            // Los casos de uso van directamente en la carpeta del servicio (sin subcarpetas)
            await fs.ensureDir(paths.domainUseCases);
            // Generar Use Case si tiene responseFields (puede o no tener request)
            if (operation.responseFields && operation.responseFields.length > 0) {
                const hasRequest = operation.fields && operation.fields.length > 0;
                const requestDTOName = `I${toPascalCase(serviceName)}${cleanOperationName}RequestDTO`;
                const baseResponseDTOName = `I${toPascalCase(serviceName)}${cleanOperationName}ResponseDTO`;
                const responseDTOName = operation.isResponseArray ? `${baseResponseDTOName}[]` : baseResponseDTOName;
                const useCaseClassName = `${toPascalCase(serviceName)}${cleanOperationName}UseCase`;
                // Convertir operationName a camelCase para el método del repository
                const operationCamelCase = operationName
                    .replace(/_/g, '-')
                    .split('-')
                    .map((word, index) => index === 0 ? word : word.charAt(0).toUpperCase() + word.slice(1))
                    .join('');
                // Seguir el patrón de entities: usar un solo mapper y estructura simple
                const dtoImports = hasRequest ? `${requestDTOName}, ${baseResponseDTOName}` : baseResponseDTOName;
                const useCaseInterface = hasRequest ? `UseCase<${requestDTOName}, ${responseDTOName} | null>` : `UseCase<any, ${responseDTOName} | null>`;
                // Solo importar mapper si tiene request fields
                const mapperImport = hasRequest ? `import { Injection${toPascalCase(apiName)}Business${toPascalCase(serviceName)}${cleanOperationName}Mapper } from "@${apiName}/infrastructure/mappers/apis/${apiName}/injection/business/${serviceNameLower}/injection-${apiName}-business-${serviceNameKebab}-${operationKebab}-mapper";` : '';
                // Método execute siguiendo el patrón
                const executeParams = hasRequest ? `params: ${requestDTOName}, ` : '';
                const repositoryCall = hasRequest ? `this.repository.${operationCamelCase}(paramsEntity, config)` : `this.repository.${operationCamelCase}(config)`;
                const mapperLogic = hasRequest
                    ? `const paramsEntity = this.mapper.mapTo(params);
    return await ${repositoryCall}.then((data) => data ?? null);`
                    : `return await ${repositoryCall}.then((data) => data ?? null);`;
                const useCase = `import { IConfigDTO } from "@core/interfaces/i-config-repository-dto";
import { UseCase } from "@core/interfaces/use-case";
import { ${dtoImports} } from "@${apiName}/domain/models/apis/${apiName}/business/${serviceNameLower}";
${mapperImport}
import { Injection${toPascalCase(apiName)}BusinessRepository } from "@${apiName}/infrastructure/repositories/apis/${apiName}/repositories/injection/business/injection-${apiName}-business-repository";

export class ${useCaseClassName} implements ${useCaseInterface} {
  private static instance: ${useCaseClassName};
  private repository = Injection${toPascalCase(apiName)}BusinessRepository.${toPascalCase(serviceName)}Repository();${hasRequest ? `
  private mapper = Injection${toPascalCase(apiName)}Business${toPascalCase(serviceName)}${cleanOperationName}Mapper.${toPascalCase(serviceName)}${cleanOperationName}RequestMapper();` : ''}

  public static getInstance(): ${useCaseClassName} {
    if (!${useCaseClassName}.instance)
      ${useCaseClassName}.instance = new ${useCaseClassName}();
    return ${useCaseClassName}.instance;
  }

  public async execute(${executeParams}config?: IConfigDTO): Promise<${responseDTOName} | null> {
    ${mapperLogic}
  }
}`;
                await fs.writeFile(path.join(paths.domainUseCases, `${serviceNameKebab}-${operationKebab}-use-case.ts`), useCase);
                console.log(chalk_1.default.green(`✅ Use Case: ${serviceNameKebab}-${operationKebab}-use-case.ts`));
            }
        }
    }
    else {
        console.log(chalk_1.default.yellow('⚠️  No se generaron use cases porque no hay operaciones de negocio detectadas.'));
    }
}
async function generateBusinessFacades(serviceName, paths, schema, apiName = 'platform') {
    if (schema?.businessOperations && schema.businessOperations.length > 0) {
        const serviceNameLower = serviceName.toLowerCase();
        const serviceNameKebab = serviceName.replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, '');
        const facadeFilePath = path.join(paths.facades, `${serviceNameKebab}-facade.ts`);
        const facadeClassName = `${toPascalCase(serviceName)}Facade`;
        // Recopilar nuevos DTOs, use case instances y métodos
        const newDtos = new Set();
        const newUseCaseInstances = [];
        const newMethods = [];
        schema.businessOperations
            .filter(operation => operation.responseFields && operation.responseFields.length > 0)
            .forEach(operation => {
            const rawOperationName = getOperationNameFromPath(operation.path, operation.operationId.toLowerCase());
            const operationName = rawOperationName.replace(/_/g, '-');
            const cleanOperationName = operationName
                .replace(/_/g, '-')
                .split('-')
                .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
                .join('');
            // Convertir operationName a camelCase
            const operationCamelCase = operationName
                .replace(/_/g, '-')
                .split('-')
                .map((word, index) => index === 0 ? word : word.charAt(0).toUpperCase() + word.slice(1))
                .join('');
            const hasRequest = operation.fields && operation.fields.length > 0;
            // Agregar DTOs
            if (hasRequest) {
                newDtos.add(`I${toPascalCase(serviceName)}${cleanOperationName}RequestDTO`);
            }
            newDtos.add(`I${toPascalCase(serviceName)}${cleanOperationName}ResponseDTO`);
            // Agregar use case instance
            newUseCaseInstances.push(`  private readonly ${operationCamelCase}UseCase = Injection${toPascalCase(apiName)}Business${toPascalCase(serviceName)}UseCase.${toPascalCase(serviceName)}${cleanOperationName}UseCase();`);
            // Agregar método
            const params = hasRequest ? `params: I${toPascalCase(serviceName)}${cleanOperationName}RequestDTO, ` : '';
            const args = hasRequest ? 'params, config' : 'config';
            const baseResponseType = `I${toPascalCase(serviceName)}${cleanOperationName}ResponseDTO`;
            const responseType = operation.isResponseArray ? `${baseResponseType}[]` : baseResponseType;
            newMethods.push(`  public async ${operationCamelCase}(${params}config?: IConfigDTO): Promise<${responseType} | null> {
    return await this.${operationCamelCase}UseCase.execute(${args});
  }`);
        });
        // Leer archivo existente si existe
        let existingDtos = new Set();
        let existingUseCaseInstances = [];
        let existingMethods = [];
        if (await fs.pathExists(facadeFilePath)) {
            try {
                const existingContent = await fs.readFile(facadeFilePath, 'utf-8');
                // Extraer DTOs existentes
                const dtoImportMatch = existingContent.match(/import \{([^}]+)\} from "@\w+\/domain\/models\/apis\/\w+\/business\/\w+";/);
                if (dtoImportMatch) {
                    dtoImportMatch[1].split(',').forEach(dto => {
                        const trimmed = dto.trim();
                        if (trimmed && !newDtos.has(trimmed)) {
                            existingDtos.add(trimmed);
                        }
                    });
                }
                // Extraer use case instances existentes
                const useCaseInstanceRegex = /private readonly (\w+UseCase) = Injection[^;]+;/g;
                const useCaseMatches = existingContent.matchAll(useCaseInstanceRegex);
                const newUseCaseNames = new Set(newUseCaseInstances.map(inst => {
                    const match = inst.match(/private readonly (\w+UseCase)/);
                    return match ? match[1] : '';
                }));
                for (const match of useCaseMatches) {
                    const useCaseName = match[1];
                    if (!newUseCaseNames.has(useCaseName)) {
                        existingUseCaseInstances.push(`  ${match[0]}`);
                    }
                }
                // IMPORTANTE: Detectar métodos que usan use cases pero no tienen la instancia declarada
                // Esto puede pasar si el archivo fue corrompido antes
                const methodUseCaseRegex = /this\.(\w+UseCase)\.execute\(/g;
                const methodUseCaseMatches = existingContent.matchAll(methodUseCaseRegex);
                const declaredUseCases = new Set([
                    ...newUseCaseNames,
                    ...existingUseCaseInstances.map(inst => {
                        const match = inst.match(/private readonly (\w+UseCase)/);
                        return match ? match[1] : '';
                    })
                ]);
                for (const match of methodUseCaseMatches) {
                    const useCaseName = match[1];
                    if (!declaredUseCases.has(useCaseName)) {
                        // Reconstruir la instancia del use case basándose en el nombre del método
                        // Ejemplo: loginUseCase -> AuthLoginUseCase
                        const methodName = useCaseName.replace(/UseCase$/, '');
                        const useCaseClassName = `${toPascalCase(serviceName)}${methodName.charAt(0).toUpperCase() + methodName.slice(1)}UseCase`;
                        existingUseCaseInstances.push(`  private readonly ${useCaseName} = Injection${toPascalCase(apiName)}Business${toPascalCase(serviceName)}UseCase.${useCaseClassName}();`);
                        declaredUseCases.add(useCaseName);
                    }
                }
                // Extraer métodos públicos existentes
                const methodRegex = /public async (\w+)\([^)]*\): Promise<[^{]+\{[^}]+\}/g;
                const methodMatches = existingContent.matchAll(methodRegex);
                const newMethodNames = new Set(newMethods.map(m => {
                    const match = m.match(/public async (\w+)\(/);
                    return match ? match[1] : '';
                }));
                for (const match of methodMatches) {
                    const methodName = match[1];
                    if (!newMethodNames.has(methodName)) {
                        existingMethods.push(`  ${match[0]}`);
                    }
                }
                console.log(chalk_1.default.blue(`ℹ️  Facade existente detectado - Agregando ${newMethods.length} método(s) nuevo(s), manteniendo ${existingMethods.length} método(s) existente(s)`));
            }
            catch (error) {
                console.log(chalk_1.default.yellow(`⚠️  No se pudo leer el archivo existente: ${error}`));
            }
        }
        // Combinar DTOs, use case instances y métodos
        const allDtos = [...Array.from(existingDtos), ...Array.from(newDtos)];
        const allUseCaseInstances = [...existingUseCaseInstances, ...newUseCaseInstances];
        const allMethods = [...existingMethods, ...newMethods];
        // Generar el archivo completo
        const dtoImports = allDtos.join(',\n  ');
        const useCaseInjectionImport = `import { Injection${toPascalCase(apiName)}Business${toPascalCase(serviceName)}UseCase } from "@${apiName}/domain/services/use_cases/apis/${apiName}/injection/business/injection-${apiName}-business-${serviceNameKebab}-use-case";`;
        const facade = `import { IConfigDTO } from "@core/interfaces/i-config-repository-dto";
import {
  ${dtoImports},
} from "@${apiName}/domain/models/apis/${apiName}/business/${serviceNameLower}";
${useCaseInjectionImport}

export class ${facadeClassName} {
  private static instance: ${facadeClassName};

${allUseCaseInstances.join('\n')}

  public static getInstance(): ${facadeClassName} {
    if (!${facadeClassName}.instance)
      ${facadeClassName}.instance = new ${facadeClassName}();
    return ${facadeClassName}.instance;
  }

${allMethods.join('\n\n')}
}`;
        await fs.writeFile(facadeFilePath, facade);
        console.log(chalk_1.default.green(`✅ Facade: ${serviceNameKebab}-facade.ts ${existingMethods.length > 0 ? '(actualizado)' : ''}`));
    }
    else {
        console.log(chalk_1.default.yellow('⚠️  No se generó facade porque no hay operaciones de negocio detectadas.'));
    }
}
async function generateMapperInjectionPerOperation(serviceName, paths, schema, apiName = 'platform') {
    if (schema?.businessOperations && schema.businessOperations.length > 0) {
        const serviceNameLower = serviceName.toLowerCase();
        const serviceNameKebab = serviceName.replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, '');
        // Crear directorio injection en la ubicación correcta
        await fs.ensureDir(paths.injectionMappers);
        for (const operation of schema.businessOperations) {
            const rawOperationName = getOperationNameFromPath(operation.path, operation.operationId.toLowerCase());
            const operationName = rawOperationName.replace(/_/g, '-');
            const operationKebab = operationName.replace(/_/g, '-');
            const cleanOperationName = operationName
                .replace(/_/g, '-')
                .split('-')
                .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
                .join('');
            // Recopilar todos los mappers de esta operación
            const mapperNames = [];
            const mapperMethods = [];
            // Request mapper (si existe)
            if (operation.fields && operation.fields.length > 0) {
                const requestMapperName = `${toPascalCase(serviceName)}${cleanOperationName}RequestMapper`;
                mapperNames.push(requestMapperName);
                mapperMethods.push(`  public static ${requestMapperName}(): ${requestMapperName} {
    return ${requestMapperName}.getInstance();
  }`);
            }
            // Response mapper (si existe)
            if (operation.responseFields && operation.responseFields.length > 0) {
                const responseMapperName = `${toPascalCase(serviceName)}${cleanOperationName}ResponseMapper`;
                mapperNames.push(responseMapperName);
                mapperMethods.push(`  public static ${responseMapperName}(): ${responseMapperName} {
    return ${responseMapperName}.getInstance();
  }`);
                // Mappers anidados de response (si existen)
                const responseNestedMappers = await collectNestedMappersForOperation(operation, 'response', serviceName, operationName);
                for (const nestedMapper of responseNestedMappers) {
                    const nestedMapperName = nestedMapper.className;
                    // Generar nombre de método abreviado removiendo el prefijo del servicio y operación
                    // Ejemplo: AuthLoginPlatformConfigurationResponseMapper -> PlatformConfigurationResponseMapper
                    const formattedServiceName = toPascalCase(serviceName);
                    const methodName = nestedMapperName.replace(new RegExp(`^${formattedServiceName}${cleanOperationName}`, ''), '');
                    mapperNames.push(nestedMapperName);
                    mapperMethods.push(`  public static ${methodName}(): ${nestedMapperName} {
    return ${nestedMapperName}.getInstance();
  }`);
                }
            }
            // Mappers anidados de request (si existen)
            if (operation.fields && operation.fields.length > 0) {
                const requestNestedMappers = await collectNestedMappersForOperation(operation, 'request', serviceName, operationName);
                for (const nestedMapper of requestNestedMappers) {
                    const nestedMapperName = nestedMapper.className;
                    // Generar nombre de método abreviado removiendo el prefijo del servicio y operación
                    // Ejemplo: AvailabilityAppointmentTableFilterManagerRequestMapper -> FilterManagerRequestMapper
                    const formattedServiceName = toPascalCase(serviceName);
                    const methodName = nestedMapperName.replace(new RegExp(`^${formattedServiceName}${cleanOperationName}`, ''), '');
                    mapperNames.push(nestedMapperName);
                    mapperMethods.push(`  public static ${methodName}(): ${nestedMapperName} {
    return ${nestedMapperName}.getInstance();
  }`);
                }
            }
            // Solo crear injection si hay mappers
            if (mapperNames.length > 0) {
                const injectionClassName = `Injection${toPascalCase(apiName)}Business${toPascalCase(serviceName)}${cleanOperationName}Mapper`;
                // Generar import unificado usando el index.ts
                const importStatement = `import { 
  ${mapperNames.join(',\n  ')}
} from "@${apiName}/infrastructure/mappers/apis/${apiName}/business/${serviceNameLower}";`;
                const injectionContent = `${importStatement}

export class ${injectionClassName} {
${mapperMethods.join('\n\n')}
}`;
                // Generar archivo en la ubicación correcta: /infrastructure/mappers/apis/platform/injection/business/auth/
                await fs.writeFile(path.join(paths.injectionMappers, `injection-${apiName}-business-${serviceNameKebab}-${operationKebab}-mapper.ts`), injectionContent);
                console.log(chalk_1.default.green(`✅ Injection Mapper (${operationName}): injection-${apiName}-business-${serviceNameKebab}-${operationKebab}-mapper.ts`));
            }
        }
    }
    else {
        console.log(chalk_1.default.yellow('⚠️  No se generaron injection files porque no hay operaciones de negocio detectadas.'));
    }
}
async function collectNestedMappersForOperation(operation, type, serviceName, operationName) {
    const fields = type === 'request' ? operation.fields : operation.responseFields;
    const nestedMappers = [];
    const generated = new Set();
    function processNestedFields(field, serviceName, operationName) {
        // Solo generar mappers para interfaces, NO para enums (los enums se mapean directamente)
        if (field.type && !['string', 'number', 'boolean', 'any', 'object', 'array'].includes(field.type) && !field.isEnum && !generated.has(field.type)) {
            generated.add(field.type);
            // Aplicar el patrón correcto: <flujo>-<proceso>-<tipo>-<request/response>-mapper
            const serviceNameKebab = serviceName.toLowerCase();
            const operationKebab = operationName.replace(/_/g, '-');
            let typeKebab = field.type.replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, '').replace(/[\[\]]/g, 'array');
            const suffix = type === 'request' ? 'request' : 'response';
            // Detectar y evitar duplicación de operaciones en el nombre del tipo para archivos
            const operationInFileName = operationKebab.toLowerCase();
            if (typeKebab.includes(operationInFileName)) {
                // Eliminar la operación del tipo: user-login-response -> user-response
                typeKebab = typeKebab.replace(new RegExp(`-${operationInFileName}`, 'gi'), '');
            }
            const needsSuffix = !field.type.toLowerCase().endsWith('response') && !field.type.toLowerCase().endsWith('request');
            const fileName = needsSuffix
                ? `${serviceNameKebab}-${operationKebab}-${typeKebab}-${suffix}-mapper`
                : `${serviceNameKebab}-${operationKebab}-${typeKebab}-mapper`;
            // Para el nombre de la clase: <Flujo><Proceso><Tipo><Request/Response>Mapper
            const formattedServiceName = toPascalCase(serviceName);
            const cleanOperationName = operationName
                .replace(/_/g, '-')
                .split('-')
                .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
                .join('');
            const formattedFieldType = toPascalCase(field.type);
            const classSuffix = type === 'request' ? 'Request' : 'Response';
            // Limpiar caracteres especiales para nombres de clase válidos
            const cleanFormattedType = formattedFieldType.replace(/[\[\]]/g, 'Array');
            // Aplicar la misma lógica de limpieza simplificada que en generateIndividualNestedMapper
            let finalTypeName = cleanFormattedType;
            // Remover sufijos redundantes para obtener nombre base limpio (igual que en generateIndividualNestedMapper)
            finalTypeName = finalTypeName.replace(/LoginResponse$|LoginRequest$|Response$|Request$/, '');
            // Generar nombre de clase consistente con la lógica de generateIndividualNestedMapper
            const className = `${formattedServiceName}${cleanOperationName}${finalTypeName}${classSuffix}Mapper`;
            nestedMappers.push({
                className,
                fileName
            });
            // Procesar campos anidados recursivamente
            if (field.nestedFields && field.nestedFields.length > 0) {
                field.nestedFields.forEach((nestedField) => processNestedFields(nestedField, serviceName, operationName));
            }
        }
    }
    if (fields && fields.length > 0) {
        fields.forEach((field) => processNestedFields(field, serviceName, operationName));
    }
    return nestedMappers;
}
async function generateInfrastructureRepositories(serviceName, paths, schema, apiName = 'platform') {
    if (schema?.businessOperations && schema.businessOperations.length > 0) {
        const serviceNameLower = serviceName.toLowerCase();
        const serviceNameKebab = serviceName.replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, '');
        const repositoryClassName = `${toPascalCase(serviceName)}Repository`;
        // Generar imports para todas las DTOs y Entities necesarias
        const allImports = {
            dtos: new Set(),
            entities: new Set(),
            mapperImports: new Set(),
            mapperInstances: []
        };
        // Generar métodos para todas las operaciones
        const methods = [];
        for (const operation of schema.businessOperations) {
            const rawOperationName = getOperationNameFromPath(operation.path, operation.operationId.toLowerCase());
            const operationName = rawOperationName.replace(/_/g, '-');
            const operationKebab = operationName.replace(/_/g, '-');
            const cleanOperationName = operationName
                .replace(/_/g, '-')
                .split('-')
                .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
                .join('');
            // Generar método si tiene responseFields (puede o no tener request)
            if (operation.responseFields && operation.responseFields.length > 0) {
                const requestDTOName = `I${toPascalCase(serviceName)}${cleanOperationName}RequestDTO`;
                const baseResponseDTOName = `I${toPascalCase(serviceName)}${cleanOperationName}ResponseDTO`;
                const responseDTOName = operation.isResponseArray ? `${baseResponseDTOName}[]` : baseResponseDTOName;
                const requestEntityName = `I${toPascalCase(serviceName)}${cleanOperationName}RequestEntity`;
                const responseEntityName = `I${toPascalCase(serviceName)}${cleanOperationName}ResponseEntity`;
                // Agregar imports (solo entities de request y DTOs de response)
                const hasRequest = operation.fields && operation.fields.length > 0;
                if (hasRequest) {
                    allImports.entities.add(requestEntityName);
                }
                // Para imports, siempre usar el nombre base sin [], los [] van solo en el tipo de retorno
                allImports.dtos.add(baseResponseDTOName);
                allImports.entities.add(responseEntityName);
                allImports.mapperImports.add(`import { Injection${toPascalCase(apiName)}Business${toPascalCase(serviceName)}${cleanOperationName}Mapper } from "@${apiName}/infrastructure/mappers/apis/${apiName}/injection/business/${serviceNameLower}/injection-${apiName}-business-${serviceNameKebab}-${operationKebab}-mapper";`);
                // Agregar instancias de mappers (solo Response mappers)
                const operationCamelCaseVar = operationName
                    .replace(/_/g, '-')
                    .split('-')
                    .map((word, index) => index === 0 ? word : word.charAt(0).toUpperCase() + word.slice(1))
                    .join('');
                // Solo agregar Response mapper (los Request mappers no se usan en el repository)
                allImports.mapperInstances.push(`  private ${operationCamelCaseVar}ResponseMapper = Injection${toPascalCase(apiName)}Business${toPascalCase(serviceName)}${cleanOperationName}Mapper.${toPascalCase(serviceName)}${cleanOperationName}ResponseMapper();`);
                // Convertir operationName a camelCase para el nombre del método
                const operationCamelCase = operationName
                    .replace(/_/g, '-')
                    .split('-')
                    .map((word, index) => index === 0 ? word : word.charAt(0).toUpperCase() + word.slice(1))
                    .join('');
                // Generar método
                const methodParams = hasRequest ? `params: ${requestEntityName}, ` : '';
                // Determinar el método HTTP correcto
                const httpMethod = (operation.method || 'post').toLowerCase();
                // Verificar si hay path parameters
                const hasPathParams = operation.pathParameters && operation.pathParameters.length > 0;
                // Construir la URL - si hay path params, interpolarlos
                const routeConstant = `CONST_${apiName.toUpperCase()}_API_ROUTES.${serviceName.toUpperCase()}_${operationName.toUpperCase().replace(/-/g, '_')}`;
                let urlExpression;
                let bodyParams;
                if (hasPathParams && operation.pathParameters) {
                    // Interpolar path params en la URL usando template string
                    const pathParamsList = operation.pathParameters.map((p) => `params.${p.name}`);
                    urlExpression = `\`\${${routeConstant}}/${pathParamsList.map((p) => `\${${p}}`).join('/')}\``;
                    // Para body, excluir los path params (solo enviar los que no son de path)
                    const bodyFields = operation.fields?.filter((f) => !f.isPathParam) || [];
                    if (bodyFields.length > 0 && (httpMethod === 'post' || httpMethod === 'put' || httpMethod === 'patch')) {
                        // Crear objeto solo con los campos que no son path params
                        const bodyFieldNames = bodyFields.map((f) => f.name);
                        bodyParams = `{ ${bodyFieldNames.map((n) => `${n}: params.${n}`).join(', ')} }`;
                    }
                    else {
                        bodyParams = '';
                    }
                }
                else {
                    urlExpression = routeConstant;
                    bodyParams = hasRequest ? 'params' : '{}';
                }
                // Construir la llamada axios según el método HTTP
                let axiosCall;
                if (httpMethod === 'get' || httpMethod === 'delete') {
                    // GET y DELETE normalmente no envían body (o usan query params)
                    axiosCall = `.${httpMethod}(${urlExpression})`;
                }
                else {
                    // POST, PUT, PATCH envían body
                    axiosCall = `.${httpMethod}(${urlExpression}, ${bodyParams || '{}'})`;
                }
                // Para ResolveRequest, usar [] solo si es un array
                const resolveEntityType = operation.isResponseArray ? `${responseEntityName}[]` : responseEntityName;
                const method = `  public async ${operationCamelCase}(
    ${methodParams}config: IConfigDTO = CONST_CORE_DTO.CONFIG
  ): Promise<${responseDTOName} | null> {
    if (config.loadService)
      return ${apiName}Axios
        ${axiosCall}
        .then(({ data }: { data: Response<${resolveEntityType}> }) => {
          const entity = this.resolve.ResolveRequest<${resolveEntityType}>(data);
          if (entity)
            return this.${operationCamelCaseVar}ResponseMapper.${operation.isResponseArray ? 'mapFromList' : 'mapFrom'}(entity);
          return null;
        });
    return null;
  }`;
                methods.push(method);
            }
        }
        // Verificar si el archivo ya existe
        const repositoryFilePath = path.join(paths.infraRepositories, `${serviceNameKebab}-repository.ts`);
        let existingMethods = [];
        let existingMapperInstances = [];
        let existingDtos = new Set();
        let existingEntities = new Set();
        let existingMapperImports = new Set();
        if (await fs.pathExists(repositoryFilePath)) {
            try {
                const existingContent = await fs.readFile(repositoryFilePath, 'utf-8');
                // Extraer métodos existentes usando un enfoque más robusto
                // Buscar desde "public async" hasta el siguiente "public async" o hasta el cierre de la clase
                const newMethodNames = new Set(methods.map(m => {
                    const match = m.match(/public async (\w+)\(/);
                    return match ? match[1] : '';
                }));
                // Dividir por "public async" para obtener cada método completo
                const methodParts = existingContent.split(/\n\s*public async /);
                for (let i = 1; i < methodParts.length; i++) {
                    const methodPart = methodParts[i];
                    const methodNameMatch = methodPart.match(/^(\w+)\(/);
                    if (methodNameMatch) {
                        const methodName = methodNameMatch[1];
                        // Solo agregar si no está en los nuevos métodos
                        if (!newMethodNames.has(methodName)) {
                            // Reconstruir el método completo
                            const fullMethod = `  public async ${methodPart.split(/\n\s*public async |^\}/m)[0].trimEnd()}`;
                            existingMethods.push(fullMethod);
                        }
                    }
                }
                // Extraer mapper instances existentes (pueden ser multilínea)
                // Buscar patrones como: private xxxResponseMapper = ... ;
                const mapperLines = existingContent.split('\n');
                const newMapperNames = new Set(allImports.mapperInstances.map(m => {
                    const match = m.match(/private (\w+ResponseMapper)/);
                    return match ? match[1] : '';
                }));
                let inMapper = false;
                let currentMapper = '';
                let currentMapperName = '';
                for (let i = 0; i < mapperLines.length; i++) {
                    const line = mapperLines[i];
                    // Detectar inicio de mapper instance
                    const mapperStartMatch = line.match(/^\s*(private (\w+ResponseMapper) =)/);
                    if (mapperStartMatch) {
                        inMapper = true;
                        currentMapperName = mapperStartMatch[2];
                        currentMapper = line;
                        // Si termina en la misma línea
                        if (line.includes(';')) {
                            inMapper = false;
                            if (!newMapperNames.has(currentMapperName)) {
                                existingMapperInstances.push(currentMapper);
                            }
                            currentMapper = '';
                            currentMapperName = '';
                        }
                    }
                    else if (inMapper) {
                        // Continuar acumulando líneas del mapper
                        currentMapper += '\n' + line;
                        // Si encontramos el final (;)
                        if (line.includes(';')) {
                            inMapper = false;
                            if (!newMapperNames.has(currentMapperName)) {
                                existingMapperInstances.push(currentMapper);
                            }
                            currentMapper = '';
                            currentMapperName = '';
                        }
                    }
                }
                // Extraer DTOs existentes
                const dtoImportMatch = existingContent.match(/import \{([^}]+)\} from "@\w+\/domain\/models\/apis\/\w+\/business\/\w+";/);
                if (dtoImportMatch) {
                    dtoImportMatch[1].split(',').forEach(dto => {
                        const trimmed = dto.trim();
                        if (trimmed && !allImports.dtos.has(trimmed)) {
                            existingDtos.add(trimmed);
                        }
                    });
                }
                // Extraer Entities existentes
                const entityImportMatch = existingContent.match(/import \{([^}]+)\} from "@\w+\/infrastructure\/entities\/apis\/\w+\/business\/\w+";/);
                if (entityImportMatch) {
                    entityImportMatch[1].split(',').forEach(entity => {
                        const trimmed = entity.trim();
                        if (trimmed && !allImports.entities.has(trimmed)) {
                            existingEntities.add(trimmed);
                        }
                    });
                }
                // Extraer mapper imports existentes
                const mapperImportRegex = /import \{ Injection\w+ \} from "@\w+\/infrastructure\/mappers[^"]+";/g;
                const mapperImportMatches = existingContent.matchAll(mapperImportRegex);
                const newMapperImportsSet = new Set(Array.from(allImports.mapperImports));
                for (const match of mapperImportMatches) {
                    if (!newMapperImportsSet.has(match[0])) {
                        existingMapperImports.add(match[0]);
                    }
                }
                console.log(chalk_1.default.blue(`ℹ️  Archivo existente detectado - Agregando ${methods.length} método(s) nuevo(s), manteniendo ${existingMethods.length} método(s) existente(s)`));
            }
            catch (error) {
                console.log(chalk_1.default.yellow(`⚠️  No se pudo leer el archivo existente: ${error}`));
            }
        }
        // Combinar DTOs, Entities y Mapper Imports
        const allDtos = [...Array.from(existingDtos), ...Array.from(allImports.dtos)];
        const allEntities = [...Array.from(existingEntities), ...Array.from(allImports.entities)];
        const allMapperImportsList = [...Array.from(existingMapperImports), ...Array.from(allImports.mapperImports)];
        const allMapperInstancesList = [...existingMapperInstances, ...allImports.mapperInstances];
        const allMethodsList = [...existingMethods, ...methods];
        // Generar el archivo completo
        const dtoImports = allDtos.join(', ');
        const entityImports = allEntities.join(', ');
        const mapperImports = allMapperImportsList.join('\n');
        const repository = `import { IConfigDTO } from "@core/interfaces/i-config-repository-dto";
import { Response } from "@core/interfaces/response";
import ${apiName}Axios from "@core/axios/${apiName}-axios";
import { CONST_${apiName.toUpperCase()}_API_ROUTES } from "@core/const";
import { CONST_CORE_DTO } from "@core/const/const-core";
import { InjectionCore } from "@core/injection/injection-core";
import { I${toPascalCase(serviceName)}Repository } from "@${apiName}/domain/services/repositories/apis/${apiName}/business/i-${serviceNameKebab}-repository";
import { ${dtoImports} } from "@${apiName}/domain/models/apis/${apiName}/business/${serviceNameLower}";
import { ${entityImports} } from "@${apiName}/infrastructure/entities/apis/${apiName}/business/${serviceNameLower}";
${mapperImports}

export class ${repositoryClassName} extends I${toPascalCase(serviceName)}Repository {

  private static instance: ${repositoryClassName};
  private readonly resolve = InjectionCore.Resolve();
${allMapperInstancesList.join('\n')}

  private constructor() {
    super();
  }

  public static getInstance(): ${repositoryClassName} {
    if (!${repositoryClassName}.instance)
      ${repositoryClassName}.instance = new ${repositoryClassName}();
    return ${repositoryClassName}.instance;
  }

${allMethodsList.join('\n\n')}
}`;
        await fs.writeFile(repositoryFilePath, repository);
        console.log(chalk_1.default.green(`✅ Infrastructure Repository: ${serviceNameKebab}-repository.ts ${existingMethods.length > 0 ? '(actualizado)' : ''}`));
    }
    else {
        console.log(chalk_1.default.yellow('⚠️  No se generaron infrastructure repositories porque no hay operaciones de negocio detectadas.'));
    }
}
async function generateRepositoryInjectionFiles(serviceName, paths, schema, apiName = 'platform') {
    if (schema?.businessOperations && schema.businessOperations.length > 0) {
        const serviceNameKebab = serviceName.replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, '');
        const injectionFilePath = path.join(paths.injectionRepositories, `injection-${apiName}-business-repository.ts`);
        // Datos del repository actual
        const currentRepository = {
            serviceName: toPascalCase(serviceName),
            import: `import { ${toPascalCase(serviceName)}Repository } from "../../business/${serviceName.toLowerCase()}/${serviceNameKebab}-repository";`,
            method: `  public static ${toPascalCase(serviceName)}Repository() { return ${toPascalCase(serviceName)}Repository.getInstance(); }`
        };
        let existingRepositories = [];
        let existingImports = [];
        // Leer archivo existente si existe
        if (await fs.pathExists(injectionFilePath)) {
            try {
                const existingContent = await fs.readFile(injectionFilePath, 'utf-8');
                // Extraer imports existentes
                const importMatches = existingContent.match(/import \{[^}]+\} from "[^"]+";/g);
                if (importMatches) {
                    existingImports = importMatches.filter(imp => !imp.includes(`${toPascalCase(serviceName)}Repository`));
                }
                // Extraer métodos existentes
                const methodMatches = existingContent.match(/public static \w+Repository\(\)[^}]+}/g);
                if (methodMatches) {
                    existingRepositories = methodMatches
                        .filter(method => !method.includes(`${toPascalCase(serviceName)}Repository`))
                        .map(method => ({ method: `  ${method}` }));
                }
            }
            catch (error) {
                console.log(chalk_1.default.yellow(`⚠️  No se pudo leer el archivo existente: ${error}`));
            }
        }
        // Combinar imports (existentes + actual)
        const allImports = [...existingImports, currentRepository.import].join('\n');
        // Combinar métodos (existentes + actual)
        const allMethods = [...existingRepositories.map(r => r.method), currentRepository.method].join('\n');
        // Generar archivo completo
        const injectionFile = `${allImports}

export class Injection${toPascalCase(apiName)}BusinessRepository {
${allMethods}
}`;
        await fs.writeFile(injectionFilePath, injectionFile);
        console.log(chalk_1.default.green(`✅ Repository Injection: injection-${apiName}-business-repository.ts (actualizado)`));
    }
    else {
        console.log(chalk_1.default.yellow('⚠️  No se generó repository injection porque no hay operaciones de negocio detectadas.'));
    }
}
async function generateBusinessInjectionFiles(serviceName, paths, schema, apiName = 'platform') {
    if (schema?.businessOperations && schema.businessOperations.length > 0) {
        const serviceNameKebab = serviceName.replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, '');
        const useCaseInjectionFilePath = path.join(paths.injectionUseCases, `injection-${apiName}-business-${serviceNameKebab}-use-case.ts`);
        // 1. Use Case Injection - MERGE INCREMENTAL
        const newUseCaseImports = [];
        const newUseCaseMethods = [];
        schema.businessOperations
            .filter(operation => operation.responseFields && operation.responseFields.length > 0)
            .forEach(operation => {
            const rawOperationName = getOperationNameFromPath(operation.path, operation.operationId.toLowerCase());
            const operationName = rawOperationName.replace(/_/g, '-');
            const operationKebab = operationName.replace(/_/g, '-');
            const cleanOperationName = operationName
                .replace(/_/g, '-')
                .split('-')
                .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
                .join('');
            const useCaseClassName = `${toPascalCase(serviceName)}${cleanOperationName}UseCase`;
            newUseCaseImports.push(`import { ${useCaseClassName} } from "@${apiName}/domain/services/use_cases/apis/${apiName}/business/${serviceName.toLowerCase()}/${serviceNameKebab}-${operationKebab}-use-case";`);
            newUseCaseMethods.push(`  public static ${useCaseClassName}(): ${useCaseClassName} {
    return ${useCaseClassName}.getInstance();
  }`);
        });
        let existingImports = [];
        let existingMethods = [];
        // Leer archivo existente si existe
        if (await fs.pathExists(useCaseInjectionFilePath)) {
            try {
                const existingContent = await fs.readFile(useCaseInjectionFilePath, 'utf-8');
                // Extraer imports existentes
                const importMatches = existingContent.match(/import \{[^}]+\} from "[^"]+";/g);
                if (importMatches) {
                    const newUseCaseClassNames = new Set(newUseCaseImports.map(imp => {
                        const match = imp.match(/import \{ (\w+) \}/);
                        return match ? match[1] : '';
                    }));
                    existingImports = importMatches.filter(imp => {
                        const match = imp.match(/import \{ (\w+) \}/);
                        const className = match ? match[1] : '';
                        return className && !newUseCaseClassNames.has(className);
                    });
                }
                // Extraer métodos existentes
                const methodRegex = /public static (\w+UseCase)\(\):[^}]+\}/g;
                const methodMatches = existingContent.matchAll(methodRegex);
                const newMethodNames = new Set(newUseCaseMethods.map(m => {
                    const match = m.match(/public static (\w+UseCase)\(\)/);
                    return match ? match[1] : '';
                }));
                for (const match of methodMatches) {
                    const methodName = match[1];
                    if (!newMethodNames.has(methodName)) {
                        existingMethods.push(`  ${match[0]}`);
                    }
                }
                console.log(chalk_1.default.blue(`ℹ️  Use Case Injection existente - Agregando ${newUseCaseMethods.length} método(s) nuevo(s), manteniendo ${existingMethods.length} método(s) existente(s)`));
            }
            catch (error) {
                console.log(chalk_1.default.yellow(`⚠️  No se pudo leer el archivo existente: ${error}`));
            }
        }
        // Combinar imports y métodos (existentes + nuevos)
        const allImports = [...existingImports, ...newUseCaseImports].join('\n');
        const allMethods = [...existingMethods, ...newUseCaseMethods].join('\n\n');
        const useCaseInjection = `${allImports}

export class Injection${toPascalCase(apiName)}Business${toPascalCase(serviceName)}UseCase {
${allMethods}
}`;
        await fs.writeFile(useCaseInjectionFilePath, useCaseInjection);
        console.log(chalk_1.default.green(`✅ Use Case Injection: injection-${apiName}-business-${serviceNameKebab}-use-case.ts ${existingMethods.length > 0 ? '(actualizado)' : ''}`));
        // 2. Facade Injection (acumulativo como repositories)
        await generateFacadeInjectionFiles(serviceName, paths, apiName);
    }
    else {
        console.log(chalk_1.default.yellow('⚠️  No se generaron archivos de inyección porque no hay operaciones de negocio detectadas.'));
    }
}
async function generateFacadeInjectionFiles(serviceName, paths, apiName = 'platform') {
    const serviceNameKebab = serviceName.replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, '');
    const injectionFilePath = path.join(paths.injectionFacades, `injection-${apiName}-business-facade.ts`);
    // Datos del facade actual
    const currentFacade = {
        serviceName: toPascalCase(serviceName),
        import: `import { ${toPascalCase(serviceName)}Facade } from "@${apiName}/facade/apis/${apiName}/business/${serviceNameKebab}-facade";`,
        method: `    public static ${toPascalCase(serviceName)}Facade() { return ${toPascalCase(serviceName)}Facade.getInstance(); }`
    };
    let existingFacades = [];
    let existingImports = [];
    // Leer archivo existente si existe
    if (await fs.pathExists(injectionFilePath)) {
        try {
            const existingContent = await fs.readFile(injectionFilePath, 'utf-8');
            // Extraer imports existentes
            const importMatches = existingContent.match(/import \{[^}]+\} from "[^"]+";/g);
            if (importMatches) {
                existingImports = importMatches.filter(imp => !imp.includes(`${toPascalCase(serviceName)}Facade`));
            }
            // Extraer métodos existentes
            const methodMatches = existingContent.match(/public static \w+Facade\(\)[^}]+}/g);
            if (methodMatches) {
                existingFacades = methodMatches
                    .filter(method => !method.includes(`${toPascalCase(serviceName)}Facade`))
                    .map(method => ({ method: `    ${method}` }));
            }
        }
        catch (error) {
            console.log(chalk_1.default.yellow(`⚠️  No se pudo leer el archivo existente: ${error}`));
        }
    }
    // Combinar imports (existentes + actual)
    const allImports = [...existingImports, currentFacade.import].join('\n');
    // Combinar métodos (existentes + actual)
    const allMethods = [...existingFacades.map(f => f.method), currentFacade.method].join('\n');
    // Generar archivo completo
    const injectionFile = `${allImports}

export class Injection${toPascalCase(apiName)}BusinessFacade {
${allMethods}
}`;
    await fs.writeFile(injectionFilePath, injectionFile);
    console.log(chalk_1.default.green(`✅ Facade Injection: injection-${apiName}-business-facade.ts (actualizado)`));
}
//# sourceMappingURL=business-flow-generator.js.map