workspace/projects/erp-suite/apps/verticales/construccion/backend/scripts/validate-constants-usage.ts
rckrdmrd 513a86ceee
Some checks are pending
CI Pipeline / changes (push) Waiting to run
CI Pipeline / core (push) Blocked by required conditions
CI Pipeline / trading-backend (push) Blocked by required conditions
CI Pipeline / trading-data-service (push) Blocked by required conditions
CI Pipeline / trading-frontend (push) Blocked by required conditions
CI Pipeline / erp-core (push) Blocked by required conditions
CI Pipeline / erp-mecanicas (push) Blocked by required conditions
CI Pipeline / gamilit-backend (push) Blocked by required conditions
CI Pipeline / gamilit-frontend (push) Blocked by required conditions
Major update: orchestration system, catalog references, and multi-project enhancements
Core:
- Add catalog reference implementations (auth, payments, notifications, websocket, etc.)
- New agent profiles: Database Auditor, Integration Validator, LLM Agent, Policy Auditor, Trading Strategist
- Update SIMCO directives and add escalation/git guidelines
- Add deployment inventory and audit execution reports

Projects:
- erp-suite: DevOps configs, Dockerfiles, shared libs, vertical enhancements
- gamilit: Test structure, admin controllers, service refactoring, husky/commitlint
- trading-platform: MT4 gateway, auth controllers, admin frontend, deployment scripts
- platform_marketing_content: Full DevOps setup, tests, Docker configs
- betting-analytics/inmobiliaria-analytics: Initial app structure

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-12 22:53:55 -06:00

386 lines
11 KiB
TypeScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env ts-node
/**
* Validate Constants Usage - SSOT Enforcement
*
* Este script detecta hardcoding de schemas, tablas, rutas API y enums
* que deberian estar usando las constantes centralizadas del SSOT.
*
* Ejecutar: npm run validate:constants
*
* @author Architecture-Analyst
* @date 2025-12-12
*/
import * as fs from 'fs';
import * as path from 'path';
// =============================================================================
// CONFIGURACION
// =============================================================================
interface ValidationPattern {
pattern: RegExp;
message: string;
severity: 'P0' | 'P1' | 'P2';
suggestion: string;
exclude?: RegExp[];
}
const PATTERNS: ValidationPattern[] = [
// Database Schemas
{
pattern: /['"`]auth['"`](?!\s*:)/g,
message: 'Hardcoded schema "auth"',
severity: 'P0',
suggestion: 'Usa DB_SCHEMAS.AUTH',
exclude: [/from\s+['"`]\.\/database\.constants['"`]/],
},
{
pattern: /['"`]construction['"`](?!\s*:)/g,
message: 'Hardcoded schema "construction"',
severity: 'P0',
suggestion: 'Usa DB_SCHEMAS.CONSTRUCTION',
},
{
pattern: /['"`]hr['"`](?!\s*:)(?!\.entity)/g,
message: 'Hardcoded schema "hr"',
severity: 'P0',
suggestion: 'Usa DB_SCHEMAS.HR',
},
{
pattern: /['"`]hse['"`](?!\s*:)(?!\/)/g,
message: 'Hardcoded schema "hse"',
severity: 'P0',
suggestion: 'Usa DB_SCHEMAS.HSE',
},
{
pattern: /['"`]estimates['"`](?!\s*:)/g,
message: 'Hardcoded schema "estimates"',
severity: 'P0',
suggestion: 'Usa DB_SCHEMAS.ESTIMATES',
},
{
pattern: /['"`]infonavit['"`](?!\s*:)/g,
message: 'Hardcoded schema "infonavit"',
severity: 'P0',
suggestion: 'Usa DB_SCHEMAS.INFONAVIT',
},
{
pattern: /['"`]inventory['"`](?!\s*:)/g,
message: 'Hardcoded schema "inventory"',
severity: 'P0',
suggestion: 'Usa DB_SCHEMAS.INVENTORY',
},
{
pattern: /['"`]purchase['"`](?!\s*:)/g,
message: 'Hardcoded schema "purchase"',
severity: 'P0',
suggestion: 'Usa DB_SCHEMAS.PURCHASE',
},
// API Routes
{
pattern: /['"`]\/api\/v1\/proyectos['"`]/g,
message: 'Hardcoded API route "/api/v1/proyectos"',
severity: 'P0',
suggestion: 'Usa API_ROUTES.PROYECTOS.BASE',
},
{
pattern: /['"`]\/api\/v1\/fraccionamientos['"`]/g,
message: 'Hardcoded API route "/api/v1/fraccionamientos"',
severity: 'P0',
suggestion: 'Usa API_ROUTES.FRACCIONAMIENTOS.BASE',
},
{
pattern: /['"`]\/api\/v1\/employees['"`]/g,
message: 'Hardcoded API route "/api/v1/employees"',
severity: 'P0',
suggestion: 'Usa API_ROUTES.EMPLOYEES.BASE',
},
{
pattern: /['"`]\/api\/v1\/incidentes['"`]/g,
message: 'Hardcoded API route "/api/v1/incidentes"',
severity: 'P0',
suggestion: 'Usa API_ROUTES.INCIDENTES.BASE',
},
// Common Table Names
{
pattern: /FROM\s+proyectos(?!\s+AS|\s+WHERE)/gi,
message: 'Hardcoded table name "proyectos"',
severity: 'P1',
suggestion: 'Usa DB_TABLES.CONSTRUCTION.PROYECTOS',
},
{
pattern: /FROM\s+fraccionamientos(?!\s+AS|\s+WHERE)/gi,
message: 'Hardcoded table name "fraccionamientos"',
severity: 'P1',
suggestion: 'Usa DB_TABLES.CONSTRUCTION.FRACCIONAMIENTOS',
},
{
pattern: /FROM\s+employees(?!\s+AS|\s+WHERE)/gi,
message: 'Hardcoded table name "employees"',
severity: 'P1',
suggestion: 'Usa DB_TABLES.HR.EMPLOYEES',
},
{
pattern: /FROM\s+incidentes(?!\s+AS|\s+WHERE)/gi,
message: 'Hardcoded table name "incidentes"',
severity: 'P1',
suggestion: 'Usa DB_TABLES.HSE.INCIDENTES',
},
// Status Values
{
pattern: /status\s*===?\s*['"`]active['"`]/gi,
message: 'Hardcoded status "active"',
severity: 'P1',
suggestion: 'Usa PROJECT_STATUS.ACTIVE o USER_STATUS.ACTIVE',
},
{
pattern: /status\s*===?\s*['"`]borrador['"`]/gi,
message: 'Hardcoded status "borrador"',
severity: 'P1',
suggestion: 'Usa BUDGET_STATUS.DRAFT o ESTIMATION_STATUS.DRAFT',
},
{
pattern: /status\s*===?\s*['"`]aprobado['"`]/gi,
message: 'Hardcoded status "aprobado"',
severity: 'P1',
suggestion: 'Usa BUDGET_STATUS.APPROVED o ESTIMATION_STATUS.APPROVED',
},
// Role Names
{
pattern: /role\s*===?\s*['"`]admin['"`]/gi,
message: 'Hardcoded role "admin"',
severity: 'P0',
suggestion: 'Usa ROLES.ADMIN',
},
{
pattern: /role\s*===?\s*['"`]supervisor['"`]/gi,
message: 'Hardcoded role "supervisor"',
severity: 'P1',
suggestion: 'Usa ROLES.SUPERVISOR_OBRA o ROLES.SUPERVISOR_HSE',
},
];
// Archivos a excluir
const EXCLUDED_PATHS = [
'node_modules',
'dist',
'.git',
'coverage',
'database.constants.ts',
'api.constants.ts',
'enums.constants.ts',
'index.ts',
'.sql',
'.md',
'.json',
'.yml',
'.yaml',
];
// Extensiones a validar
const VALID_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx'];
// =============================================================================
// TIPOS
// =============================================================================
interface Violation {
file: string;
line: number;
column: number;
pattern: string;
message: string;
severity: 'P0' | 'P1' | 'P2';
suggestion: string;
context: string;
}
// =============================================================================
// FUNCIONES
// =============================================================================
function shouldExclude(filePath: string): boolean {
return EXCLUDED_PATHS.some(excluded => filePath.includes(excluded));
}
function hasValidExtension(filePath: string): boolean {
return VALID_EXTENSIONS.some(ext => filePath.endsWith(ext));
}
function getFiles(dir: string): string[] {
const files: string[] = [];
if (!fs.existsSync(dir)) {
return files;
}
const items = fs.readdirSync(dir);
for (const item of items) {
const fullPath = path.join(dir, item);
const stat = fs.statSync(fullPath);
if (stat.isDirectory()) {
if (!shouldExclude(fullPath)) {
files.push(...getFiles(fullPath));
}
} else if (stat.isFile() && hasValidExtension(fullPath) && !shouldExclude(fullPath)) {
files.push(fullPath);
}
}
return files;
}
function findViolations(filePath: string, content: string, patterns: ValidationPattern[]): Violation[] {
const violations: Violation[] = [];
const lines = content.split('\n');
for (const patternConfig of patterns) {
let match: RegExpExecArray | null;
const regex = new RegExp(patternConfig.pattern.source, patternConfig.pattern.flags);
while ((match = regex.exec(content)) !== null) {
// Check exclusions
if (patternConfig.exclude) {
const shouldSkip = patternConfig.exclude.some(excludePattern =>
excludePattern.test(content)
);
if (shouldSkip) continue;
}
// Find line number
const beforeMatch = content.substring(0, match.index);
const lineNumber = beforeMatch.split('\n').length;
const lineStart = beforeMatch.lastIndexOf('\n') + 1;
const column = match.index - lineStart + 1;
violations.push({
file: filePath,
line: lineNumber,
column,
pattern: match[0],
message: patternConfig.message,
severity: patternConfig.severity,
suggestion: patternConfig.suggestion,
context: lines[lineNumber - 1]?.trim() || '',
});
}
}
return violations;
}
function formatViolation(v: Violation): string {
const severityColor = {
P0: '\x1b[31m', // Red
P1: '\x1b[33m', // Yellow
P2: '\x1b[36m', // Cyan
};
const reset = '\x1b[0m';
return `
${severityColor[v.severity]}[${v.severity}]${reset} ${v.message}
File: ${v.file}:${v.line}:${v.column}
Found: "${v.pattern}"
Context: ${v.context}
Suggestion: ${v.suggestion}
`;
}
function generateReport(violations: Violation[]): void {
const p0 = violations.filter(v => v.severity === 'P0');
const p1 = violations.filter(v => v.severity === 'P1');
const p2 = violations.filter(v => v.severity === 'P2');
console.log('\n========================================');
console.log('SSOT VALIDATION REPORT');
console.log('========================================\n');
console.log(`Total Violations: ${violations.length}`);
console.log(` P0 (Critical): ${p0.length}`);
console.log(` P1 (High): ${p1.length}`);
console.log(` P2 (Medium): ${p2.length}`);
if (violations.length > 0) {
console.log('\n----------------------------------------');
console.log('VIOLATIONS FOUND:');
console.log('----------------------------------------');
// Group by file
const byFile = violations.reduce((acc, v) => {
if (!acc[v.file]) acc[v.file] = [];
acc[v.file].push(v);
return acc;
}, {} as Record<string, Violation[]>);
for (const [file, fileViolations] of Object.entries(byFile)) {
console.log(`\n📁 ${file}`);
for (const v of fileViolations) {
console.log(formatViolation(v));
}
}
}
console.log('\n========================================');
if (p0.length > 0) {
console.log('\n❌ FAILED: P0 violations found. Fix before merging.\n');
process.exit(1);
} else if (violations.length > 0) {
console.log('\n⚠ WARNING: Non-critical violations found. Consider fixing.\n');
process.exit(0);
} else {
console.log('\n✅ PASSED: No SSOT violations found!\n');
process.exit(0);
}
}
// =============================================================================
// MAIN
// =============================================================================
function main(): void {
const backendDir = path.resolve(__dirname, '../src');
const frontendDir = path.resolve(__dirname, '../../frontend/web/src');
console.log('🔍 Validating SSOT constants usage...\n');
console.log(`Backend: ${backendDir}`);
console.log(`Frontend: ${frontendDir}`);
const allViolations: Violation[] = [];
// Scan backend
if (fs.existsSync(backendDir)) {
const backendFiles = getFiles(backendDir);
console.log(`\nScanning ${backendFiles.length} backend files...`);
for (const file of backendFiles) {
const content = fs.readFileSync(file, 'utf-8');
const violations = findViolations(file, content, PATTERNS);
allViolations.push(...violations);
}
}
// Scan frontend
if (fs.existsSync(frontendDir)) {
const frontendFiles = getFiles(frontendDir);
console.log(`Scanning ${frontendFiles.length} frontend files...`);
for (const file of frontendFiles) {
const content = fs.readFileSync(file, 'utf-8');
const violations = findViolations(file, content, PATTERNS);
allViolations.push(...violations);
}
}
generateReport(allViolations);
}
main();