FASE 7 COMPLETADA: Testing y Lanzamiento - PROYECTO FINALIZADO
Some checks failed
CI/CD Pipeline / 🧪 Tests (push) Has been cancelled
CI/CD Pipeline / 🏗️ Build (push) Has been cancelled
CI/CD Pipeline / 🚀 Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / 🚀 Deploy to Production (push) Has been cancelled
CI/CD Pipeline / 🏷️ Create Release (push) Has been cancelled
CI/CD Pipeline / 🧹 Cleanup (push) Has been cancelled

Implementados 4 módulos con agent swarm:

1. TESTING FUNCIONAL (Jest)
   - Configuración Jest + ts-jest
   - Tests unitarios: auth, booking, court (55 tests)
   - Tests integración: routes (56 tests)
   - Factories y utilidades de testing
   - Coverage configurado (70% servicios)
   - Scripts: test, test:watch, test:coverage

2. TESTING DE USUARIO (Beta)
   - Sistema de beta testers
   - Feedback con categorías y severidad
   - Beta issues tracking
   - 8 testers de prueba creados
   - API completa para gestión de feedback

3. DOCUMENTACIÓN COMPLETA
   - API.md - 150+ endpoints documentados
   - SETUP.md - Guía de instalación
   - DEPLOY.md - Deploy en VPS
   - ARCHITECTURE.md - Arquitectura del sistema
   - APP_STORE.md - Material para stores
   - Postman Collection completa
   - PM2 ecosystem config
   - Nginx config con SSL

4. GO LIVE Y PRODUCCIÓN
   - Sistema de monitoreo (logs, health checks)
   - Servicio de alertas multi-canal
   - Pre-deploy check script
   - Docker + docker-compose producción
   - Backup automatizado
   - CI/CD GitHub Actions
   - Launch checklist completo

ESTADÍSTICAS FINALES:
- Fases completadas: 7/7
- Archivos creados: 250+
- Líneas de código: 60,000+
- Endpoints API: 150+
- Tests: 110+
- Documentación: 5,000+ líneas

PROYECTO COMPLETO Y LISTO PARA PRODUCCIÓN
This commit is contained in:
2026-01-31 22:30:44 +00:00
parent e135e7ad24
commit dd10891432
61 changed files with 19256 additions and 142 deletions

View File

@@ -0,0 +1,541 @@
#!/usr/bin/env node
/**
* Script de verificación pre-deploy
* Fase 7.4 - Go Live y Soporte
*
* Este script verifica que todo esté listo antes de un despliegue a producción.
*
* Uso:
* node scripts/pre-deploy-check.js
*
* Salida:
* - Código 0 si todas las verificaciones pasan
* - Código 1 si alguna verificación falla
*/
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
// Colores para output
const colors = {
reset: '\x1b[0m',
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
cyan: '\x1b[36m',
};
// Resultados
const results = {
passed: [],
failed: [],
warnings: [],
};
/**
* Imprime un mensaje con color
*/
function print(message, color = 'reset') {
console.log(`${colors[color]}${message}${colors.reset}`);
}
/**
* Ejecuta un comando y retorna el resultado
*/
function runCommand(command, options = {}) {
try {
return execSync(command, {
encoding: 'utf-8',
stdio: options.silent ? 'pipe' : 'inherit',
...options
});
} catch (error) {
if (options.ignoreError) {
return error.stdout || '';
}
throw error;
}
}
/**
* Verifica variables de entorno requeridas
*/
function checkEnvironmentVariables() {
print('\n🔍 Verificando variables de entorno...', 'cyan');
const required = [
'DATABASE_URL',
'JWT_SECRET',
'NODE_ENV',
];
const recommended = [
'SMTP_HOST',
'SMTP_USER',
'SMTP_PASS',
'MERCADOPAGO_ACCESS_TOKEN',
'FRONTEND_URL',
'API_URL',
];
let allRequiredPresent = true;
// Verificar requeridas
for (const env of required) {
if (!process.env[env]) {
print(`${env}: NO DEFINIDA`, 'red');
results.failed.push(`Variable requerida faltante: ${env}`);
allRequiredPresent = false;
} else {
print(`${env}: Definida`, 'green');
}
}
// Verificar recomendadas
for (const env of recommended) {
if (!process.env[env]) {
print(` ⚠️ ${env}: No definida (recomendada)`, 'yellow');
results.warnings.push(`Variable recomendada faltante: ${env}`);
} else {
print(`${env}: Definida`, 'green');
}
}
if (allRequiredPresent) {
results.passed.push('Variables de entorno requeridas');
}
return allRequiredPresent;
}
/**
* Verifica conexión a base de datos
*/
async function checkDatabaseConnection() {
print('\n🔍 Verificando conexión a base de datos...', 'cyan');
try {
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
// Intentar conectar
await prisma.$connect();
// Verificar que podemos hacer queries
await prisma.$queryRaw`SELECT 1`;
// Obtener información de la BD
const dbInfo = await prisma.$queryRaw`SELECT sqlite_version() as version`;
await prisma.$disconnect();
print(` ✅ Conexión a base de datos exitosa`, 'green');
print(` 📊 Versión: ${dbInfo[0]?.version || 'N/A'}`, 'blue');
results.passed.push('Conexión a base de datos');
return true;
} catch (error) {
print(` ❌ Error de conexión: ${error.message}`, 'red');
results.failed.push(`Conexión a base de datos fallida: ${error.message}`);
return false;
}
}
/**
* Verifica migraciones pendientes
*/
async function checkPendingMigrations() {
print('\n🔍 Verificando migraciones pendientes...', 'cyan');
try {
// Generar cliente prisma primero
runCommand('npx prisma generate', { silent: true });
// Verificar estado de migraciones
const output = runCommand('npx prisma migrate status', { silent: true, ignoreError: true });
if (output.includes('Database schema is up to date') ||
output.includes('No pending migrations')) {
print(` ✅ No hay migraciones pendientes`, 'green');
results.passed.push('Migraciones de base de datos');
return true;
} else if (output.includes('pending migration')) {
print(` ⚠️ Hay migraciones pendientes`, 'yellow');
print(` Ejecute: npx prisma migrate deploy`, 'yellow');
results.warnings.push('Hay migraciones pendientes de aplicar');
return true; // Es warning, no error
} else {
print(` ✅ Estado de migraciones verificado`, 'green');
results.passed.push('Migraciones de base de datos');
return true;
}
} catch (error) {
print(` ⚠️ No se pudo verificar estado de migraciones`, 'yellow');
results.warnings.push(`Verificación de migraciones: ${error.message}`);
return true; // No es crítico para el deploy
}
}
/**
* Verifica dependencias críticas
*/
function checkDependencies() {
print('\n🔍 Verificando dependencias críticas...', 'cyan');
const criticalDeps = [
'@prisma/client',
'express',
'bcrypt',
'jsonwebtoken',
'cors',
'helmet',
'dotenv',
];
const packageJsonPath = path.join(process.cwd(), 'package.json');
if (!fs.existsSync(packageJsonPath)) {
print(` ❌ package.json no encontrado`, 'red');
results.failed.push('package.json no encontrado');
return false;
}
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
const allDeps = {
...packageJson.dependencies,
...packageJson.devDependencies
};
let allPresent = true;
for (const dep of criticalDeps) {
if (allDeps[dep]) {
print(`${dep}@${allDeps[dep]}`, 'green');
} else {
print(`${dep}: NO INSTALADO`, 'red');
results.failed.push(`Dependencia crítica faltante: ${dep}`);
allPresent = false;
}
}
if (allPresent) {
results.passed.push('Dependencias críticas instaladas');
}
return allPresent;
}
/**
* Verifica espacio en disco
*/
function checkDiskSpace() {
print('\n🔍 Verificando espacio en disco...', 'cyan');
try {
// En Linux/Mac, usar df
const platform = process.platform;
if (platform === 'linux' || platform === 'darwin') {
const output = runCommand('df -h .', { silent: true });
const lines = output.trim().split('\n');
const dataLine = lines[lines.length - 1];
const parts = dataLine.split(/\s+/);
const usedPercent = parseInt(parts[4].replace('%', ''));
if (usedPercent > 90) {
print(` ❌ Uso de disco crítico: ${usedPercent}%`, 'red');
results.failed.push(`Uso de disco crítico: ${usedPercent}%`);
return false;
} else if (usedPercent > 80) {
print(` ⚠️ Uso de disco alto: ${usedPercent}%`, 'yellow');
results.warnings.push(`Uso de disco alto: ${usedPercent}%`);
} else {
print(` ✅ Uso de disco: ${usedPercent}%`, 'green');
}
results.passed.push('Espacio en disco');
return true;
} else {
print(` ⚠️ Verificación de disco no soportada en ${platform}`, 'yellow');
results.warnings.push(`Verificación de disco no soportada en ${platform}`);
return true;
}
} catch (error) {
print(` ⚠️ No se pudo verificar espacio en disco`, 'yellow');
results.warnings.push(`Verificación de disco: ${error.message}`);
return true;
}
}
/**
* Verifica que el build funcione
*/
function checkBuild() {
print('\n🔍 Verificando build de TypeScript...', 'cyan');
try {
// Limpiar build anterior si existe
if (fs.existsSync(path.join(process.cwd(), 'dist'))) {
print(` 🧹 Limpiando build anterior...`, 'blue');
fs.rmSync(path.join(process.cwd(), 'dist'), { recursive: true });
}
// Intentar compilar
runCommand('npx tsc --noEmit', { silent: true });
print(` ✅ TypeScript compila sin errores`, 'green');
results.passed.push('Build de TypeScript');
return true;
} catch (error) {
print(` ❌ Errores de compilación de TypeScript`, 'red');
print(` ${error.message}`, 'red');
results.failed.push('Errores de compilación de TypeScript');
return false;
}
}
/**
* Verifica archivos de configuración
*/
function checkConfigurationFiles() {
print('\n🔍 Verificando archivos de configuración...', 'cyan');
const requiredFiles = [
'package.json',
'tsconfig.json',
'prisma/schema.prisma',
];
const optionalFiles = [
'.env.example',
'Dockerfile',
'docker-compose.yml',
];
let allRequiredPresent = true;
for (const file of requiredFiles) {
const filePath = path.join(process.cwd(), file);
if (fs.existsSync(filePath)) {
print(`${file}`, 'green');
} else {
print(`${file}: NO ENCONTRADO`, 'red');
results.failed.push(`Archivo requerido faltante: ${file}`);
allRequiredPresent = false;
}
}
for (const file of optionalFiles) {
const filePath = path.join(process.cwd(), file);
if (fs.existsSync(filePath)) {
print(`${file}`, 'green');
} else {
print(` ⚠️ ${file}: No encontrado (opcional)`, 'yellow');
results.warnings.push(`Archivo opcional faltante: ${file}`);
}
}
if (allRequiredPresent) {
results.passed.push('Archivos de configuración requeridos');
}
return allRequiredPresent;
}
/**
* Verifica tests (si existen)
*/
function checkTests() {
print('\n🔍 Verificando tests...', 'cyan');
// Verificar si hay tests
const testDirs = ['tests', '__tests__', 'test', 'spec'];
const hasTests = testDirs.some(dir =>
fs.existsSync(path.join(process.cwd(), dir))
);
if (!hasTests) {
print(` ⚠️ No se encontraron directorios de tests`, 'yellow');
results.warnings.push('No hay tests configurados');
return true;
}
// Verificar si jest está configurado
const packageJsonPath = path.join(process.cwd(), 'package.json');
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
if (!packageJson.scripts?.test) {
print(` ⚠️ No hay script de test configurado`, 'yellow');
results.warnings.push('Script de test no configurado');
return true;
}
try {
// Intentar ejecutar tests
runCommand('npm test', { silent: true });
print(` ✅ Tests pasaron`, 'green');
results.passed.push('Tests pasando');
return true;
} catch (error) {
print(` ❌ Algunos tests fallaron`, 'red');
results.failed.push('Tests fallidos');
return false;
}
}
/**
* Verifica endpoints críticos (requiere servidor corriendo)
*/
async function checkCriticalEndpoints() {
print('\n🔍 Verificando endpoints críticos...', 'cyan');
const baseUrl = process.env.API_URL || 'http://localhost:3000';
const endpoints = [
{ path: '/api/v1/health', name: 'Health Check' },
];
let allWorking = true;
for (const endpoint of endpoints) {
try {
const response = await fetch(`${baseUrl}${endpoint.path}`);
if (response.ok) {
print(`${endpoint.name} (${endpoint.path})`, 'green');
} else {
print(`${endpoint.name} (${endpoint.path}): HTTP ${response.status}`, 'red');
results.failed.push(`Endpoint no disponible: ${endpoint.path}`);
allWorking = false;
}
} catch (error) {
print(` ⚠️ ${endpoint.name} (${endpoint.path}): Servidor no disponible`, 'yellow');
results.warnings.push(`No se pudo verificar endpoint: ${endpoint.path}`);
// No es crítico si el servidor no está corriendo durante el check
}
}
if (allWorking) {
results.passed.push('Endpoints críticos disponibles');
}
return true;
}
/**
* Verifica seguridad básica
*/
function checkSecurityConfig() {
print('\n🔍 Verificando configuración de seguridad...', 'cyan');
const issues = [];
// Verificar JWT_SECRET
const jwtSecret = process.env.JWT_SECRET;
if (jwtSecret) {
if (jwtSecret.length < 32) {
issues.push('JWT_SECRET es muy corto (mínimo 32 caracteres)');
}
if (jwtSecret === 'your-secret-key' || jwtSecret === 'secret') {
issues.push('JWT_SECRET usa valor por defecto inseguro');
}
}
// Verificar NODE_ENV
if (process.env.NODE_ENV === 'development') {
issues.push('NODE_ENV está en development');
}
// Verificar CORS
if (process.env.FRONTEND_URL === '*') {
issues.push('CORS permite todos los orígenes (*)');
}
if (issues.length === 0) {
print(` ✅ Configuración de seguridad correcta`, 'green');
results.passed.push('Configuración de seguridad');
return true;
} else {
for (const issue of issues) {
print(` ⚠️ ${issue}`, 'yellow');
}
results.warnings.push('Problemas de seguridad encontrados');
return true; // Son warnings, no errores
}
}
/**
* Imprime resumen final
*/
function printSummary() {
print('\n' + '='.repeat(60), 'cyan');
print('RESUMEN DE VERIFICACIÓN PRE-DEPLOY', 'cyan');
print('='.repeat(60), 'cyan');
print(`\n✅ Verificaciones exitosas: ${results.passed.length}`, 'green');
results.passed.forEach(item => print(`${item}`, 'green'));
if (results.warnings.length > 0) {
print(`\n⚠️ Advertencias: ${results.warnings.length}`, 'yellow');
results.warnings.forEach(item => print(`${item}`, 'yellow'));
}
if (results.failed.length > 0) {
print(`\n❌ Errores: ${results.failed.length}`, 'red');
results.failed.forEach(item => print(`${item}`, 'red'));
}
print('\n' + '='.repeat(60), 'cyan');
if (results.failed.length === 0) {
print('✅ TODAS LAS VERIFICACIONES CRÍTICAS PASARON', 'green');
print('El sistema está listo para deploy.', 'green');
return 0;
} else {
print('❌ HAY ERRORES CRÍTICOS QUE DEBEN CORREGIRSE', 'red');
print('Por favor corrija los errores antes de deployar.', 'red');
return 1;
}
}
/**
* Función principal
*/
async function main() {
print('\n🚀 INICIANDO VERIFICACIÓN PRE-DEPLOY', 'cyan');
print(`📅 ${new Date().toISOString()}`, 'blue');
print(`📁 Directorio: ${process.cwd()}`, 'blue');
const checks = [
checkEnvironmentVariables(),
checkDependencies(),
checkConfigurationFiles(),
checkBuild(),
checkSecurityConfig(),
checkDiskSpace(),
];
// Checks asíncronos
await checkDatabaseConnection();
await checkPendingMigrations();
await checkCriticalEndpoints();
// Tests (opcional)
try {
checkTests();
} catch (e) {
// Ignorar errores de tests
}
// Imprimir resumen y salir con código apropiado
const exitCode = printSummary();
process.exit(exitCode);
}
// Ejecutar
main().catch(error => {
print(`\n💥 Error fatal: ${error.message}`, 'red');
process.exit(1);
});