limpieza de scipts

This commit is contained in:
2026-03-09 13:40:28 -06:00
parent d8232fda7c
commit 7003e5cd80
12 changed files with 265 additions and 369 deletions

View File

@@ -1,67 +0,0 @@
import re
with open("/app/app/api/v1/endpoints/auth.py", "r") as f:
content = f.read()
old = ''' # 1. Validar tenant
tenant_result = await db.execute(
select(Tenant).where(Tenant.slug == login_data.tenant_slug)
)
tenant = tenant_result.scalar_one_or_none()
if tenant is None:
logger.warning(
"Login failed - tenant not found",
email=login_data.email,
tenant_slug=login_data.tenant_slug,
)
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Tenant not found",
)'''
new = ''' # 1. Validar tenant - por slug si viene, sino detectar por email
if login_data.tenant_slug:
tenant_result = await db.execute(
select(Tenant).where(Tenant.slug == login_data.tenant_slug)
)
tenant = tenant_result.scalar_one_or_none()
if tenant is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Tenant not found",
)
else:
tenant = None'''
if old in content:
content = content.replace(old, new)
print("OK: bloque tenant reemplazado")
else:
print("ERROR: bloque no encontrado")
# Tambien actualizar la query de usuario para usar tenant o no
old2 = ''' # 2. Buscar usuario en base de datos (aislado por tenant)
query = select(User).where(
User.email == login_data.email,
User.tenant_id == tenant.id,
)'''
new2 = ''' # 2. Buscar usuario - filtrar por tenant si se detecto, sino buscar por email
if tenant:
query = select(User).where(
User.email == login_data.email,
User.tenant_id == tenant.id,
)
else:
query = select(User).where(User.email == login_data.email)'''
if old2 in content:
content = content.replace(old2, new2)
print("OK: bloque query reemplazado")
else:
print("ERROR: bloque query no encontrado")
with open("/app/app/api/v1/endpoints/auth.py", "w") as f:
f.write(content)
print("Listo")

View File

@@ -1,23 +0,0 @@
with open("/app/app/api/v1/endpoints/auth.py", "r") as f:
content = f.read()
old = ''' # Rate limiting (best-effort): by (tenant,email) to slow brute force.
ident_key = None
if settings.RATE_LIMIT_ENABLED and not settings.TESTING:
email_norm = login_data.email.strip().lower()
ident_key = cache_key("rl", "login", "id", str(tenant.id), email_norm)'''
new = ''' # Rate limiting (best-effort): by (tenant,email) to slow brute force.
ident_key = None
if settings.RATE_LIMIT_ENABLED and not settings.TESTING and tenant:
email_norm = login_data.email.strip().lower()
ident_key = cache_key("rl", "login", "id", str(tenant.id), email_norm)'''
if old in content:
content = content.replace(old, new)
print("OK: rate limiting fix aplicado")
else:
print("ERROR: bloque no encontrado")
with open("/app/app/api/v1/endpoints/auth.py", "w") as f:
f.write(content)

View File

@@ -38,7 +38,7 @@
totp_code: totpCode || undefined totp_code: totpCode || undefined
}); });
toast.success('¡Bienvenido! Has iniciado sesión correctamente'); toast.success('¡Bienvenido! Has iniciado sesión correctamente');
goto('/'); goto('/');
} catch (error: any) { } catch (error: any) {
console.error('Login error:', error); console.error('Login error:', error);
@@ -46,9 +46,9 @@
// Check if 2FA is required // Check if 2FA is required
if (error.message.includes('two-factor') || error.message.includes('2FA')) { if (error.message.includes('two-factor') || error.message.includes('2FA')) {
showTwoFactor = true; showTwoFactor = true;
errorMessage = 'Introduce el código de tu aplicación de autenticación'; errorMessage = 'Introduce el código de tu aplicación de autenticación';
} else { } else {
errorMessage = error.message || 'Error al iniciar sesión'; errorMessage = error.message || 'Error al iniciar sesión';
toast.error(errorMessage); toast.error(errorMessage);
} }
} finally { } finally {
@@ -96,8 +96,8 @@
de Servicios de TI de Servicios de TI
</h2> </h2>
<p class="text-lg text-blue-100/90 font-light max-w-lg leading-relaxed drop-shadow-md"> <p class="text-lg text-blue-100/90 font-light max-w-lg leading-relaxed drop-shadow-md">
Portal de atención a clientes. Genere tickets de soporte técnico para nuestros sistemas y Portal de atención a clientes. Genere tickets de soporte técnico para nuestros sistemas y
reciba asistencia especializada para garantizar la continuidad de su operación. reciba asistencia especializada para garantizar la continuidad de su operación.
</p> </p>
</div> </div>
@@ -135,7 +135,7 @@
<!-- Email Input --> <!-- Email Input -->
<div class="space-y-1.5"> <div class="space-y-1.5">
<label for="email" class="block text-sm font-semibold text-gray-700" <label for="email" class="block text-sm font-semibold text-gray-700"
>Correo Electrónico</label >Correo Electrónico</label
> >
<div class="relative group"> <div class="relative group">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"> <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
@@ -160,7 +160,7 @@
<!-- Password Input --> <!-- Password Input -->
<div class="space-y-1.5"> <div class="space-y-1.5">
<label for="password" class="block text-sm font-semibold text-gray-700" <label for="password" class="block text-sm font-semibold text-gray-700"
>Contraseña</label >Contraseña</label
> >
<div class="relative group"> <div class="relative group">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"> <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
@@ -176,7 +176,7 @@
bind:value={password} bind:value={password}
on:keydown={handleKeyDown} on:keydown={handleKeyDown}
class="block w-full pl-10 pr-10 py-3 bg-[#fff9c4]/0 hover:bg-gray-50 focus:bg-white border text-gray-900 border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-600 focus:border-transparent transition-all duration-200 sm:text-sm" class="block w-full pl-10 pr-10 py-3 bg-[#fff9c4]/0 hover:bg-gray-50 focus:bg-white border text-gray-900 border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-600 focus:border-transparent transition-all duration-200 sm:text-sm"
placeholder="••••••••" placeholder="••••••••"
required required
disabled={isLoading} disabled={isLoading}
/> />
@@ -187,7 +187,7 @@
bind:value={password} bind:value={password}
on:keydown={handleKeyDown} on:keydown={handleKeyDown}
class="block w-full pl-10 pr-10 py-3 bg-[#fff9c4]/0 hover:bg-gray-50 focus:bg-white border text-gray-900 border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-600 focus:border-transparent transition-all duration-200 sm:text-sm" class="block w-full pl-10 pr-10 py-3 bg-[#fff9c4]/0 hover:bg-gray-50 focus:bg-white border text-gray-900 border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-600 focus:border-transparent transition-all duration-200 sm:text-sm"
placeholder="••••••••" placeholder="••••••••"
required required
disabled={isLoading} disabled={isLoading}
/> />
@@ -221,7 +221,7 @@
class="text-sm font-medium text-blue-600 hover:text-blue-500 bg-transparent border-none p-0 cursor-pointer" class="text-sm font-medium text-blue-600 hover:text-blue-500 bg-transparent border-none p-0 cursor-pointer"
on:click={() => goto('/forgot-password')} on:click={() => goto('/forgot-password')}
> >
Olvidé mi clave Olvidé mi clave
</button> </button>
</div> </div>
</div> </div>
@@ -229,9 +229,9 @@
<!-- 2FA Input --> <!-- 2FA Input -->
<div class="space-y-4 animate-slide-up"> <div class="space-y-4 animate-slide-up">
<label for="code" class="block text-sm font-medium text-gray-700 text-center" <label for="code" class="block text-sm font-medium text-gray-700 text-center"
>Código de Verificación (2FA)</label >Código de Verificación (2FA)</label
> >
<p class="text-xs text-center text-gray-500 mb-4">Ingrese el código de 6 dígitos</p> <p class="text-xs text-center text-gray-500 mb-4">Ingrese el código de 6 dígitos</p>
<div class="relative"> <div class="relative">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"> <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
@@ -268,7 +268,7 @@
</div> </div>
<div class="mt-8 text-center text-xs text-gray-400"> <div class="mt-8 text-center text-xs text-gray-400">
© 2026 Aduanasoft. Acceso exclusivo autorizado. © 2026 Aduanasoft. Acceso exclusivo autorizado.
</div> </div>
</form> </form>
</div> </div>

View File

@@ -1,9 +1,9 @@
<script lang="ts"> <script lang="ts">
import { goto } from '$app/navigation';
import Icon from '$lib/components/Icon.svelte';
import { auth } from '$lib/stores/auth.js'; import { auth } from '$lib/stores/auth.js';
import { toast } from '$lib/stores/toast.js'; import { toast } from '$lib/stores/toast.js';
import { goto } from '$app/navigation';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import Icon from '$lib/components/Icon.svelte';
let email = ''; let email = '';
let password = ''; let password = '';
@@ -36,7 +36,7 @@
totp_code: totpCode || undefined totp_code: totpCode || undefined
}); });
toast.success('¡Bienvenido! Has iniciado sesión correctamente'); toast.success('¡Bienvenido! Has iniciado sesión correctamente');
goto('/'); goto('/');
} catch (error: any) { } catch (error: any) {
console.error('Login error:', error); console.error('Login error:', error);
@@ -44,9 +44,9 @@
// Check if 2FA is required // Check if 2FA is required
if (error.message.includes('two-factor') || error.message.includes('2FA')) { if (error.message.includes('two-factor') || error.message.includes('2FA')) {
showTwoFactor = true; showTwoFactor = true;
errorMessage = 'Introduce el código de tu aplicación de autenticación'; errorMessage = 'Introduce el código de tu aplicación de autenticación';
} else { } else {
errorMessage = error.message || 'Error al iniciar sesión'; errorMessage = error.message || 'Error al iniciar sesión';
toast.error(errorMessage); toast.error(errorMessage);
} }
} finally { } finally {
@@ -61,44 +61,50 @@
} }
</script> </script>
<svelte:head> <svelte:head>
<title>Acceso Admin - ServiceManager</title> <title>Acceso Admin - ServiceManager</title>
</svelte:head> </svelte:head>
<div class="min-h-screen flex items-center justify-center bg-gray-100 dark:bg-gray-950 p-4 font-sans"> <div
<div class="w-full max-w-5xl grid grid-cols-1 md:grid-cols-2 bg-white dark:bg-gray-900 rounded-lg shadow-xl overflow-hidden border border-gray-200 dark:border-gray-800"> class="min-h-screen flex items-center justify-center bg-gray-100 dark:bg-gray-950 p-4 font-sans"
>
<div
class="w-full max-w-5xl grid grid-cols-1 md:grid-cols-2 bg-white dark:bg-gray-900 rounded-lg shadow-xl overflow-hidden border border-gray-200 dark:border-gray-800"
>
<!-- Left Side: Internal Branding --> <!-- Left Side: Internal Branding -->
<div class="hidden md:flex flex-col justify-between p-12 bg-gray-900 text-white relative overflow-hidden"> <div
class="hidden md:flex flex-col justify-between p-12 bg-gray-900 text-white relative overflow-hidden"
>
<!-- Grid pattern overlay --> <!-- Grid pattern overlay -->
<div class="absolute inset-0 opacity-10" style="background-image: radial-gradient(white 1px, transparent 1px); background-size: 30px 30px;"></div> <div
class="absolute inset-0 opacity-10"
style="background-image: radial-gradient(white 1px, transparent 1px); background-size: 30px 30px;"
/>
<div class="relative z-10"> <div class="relative z-10">
<div class="flex items-center space-x-3 mb-6"> <div class="flex items-center space-x-3 mb-6">
<div class="p-2 bg-blue-500/20 rounded border border-blue-500/30"> <div class="p-2 bg-blue-500/20 rounded border border-blue-500/30">
<Icon name="server" className="w-6 h-6 text-blue-400" /> <Icon name="server" className="w-6 h-6 text-blue-400" />
</div> </div>
<span class="text-sm font-mono tracking-wider text-blue-400">INTERNAL_ACCESS_V2</span> <span class="text-sm font-mono tracking-wider text-blue-400">INTERNAL_ACCESS_V2</span>
</div> </div>
<h1 class="text-3xl font-bold tracking-tight mb-4"> <h1 class="text-3xl font-bold tracking-tight mb-4">Panel de Administración</h1>
Panel de Administración
</h1>
<p class="text-gray-400 text-sm leading-relaxed max-w-sm"> <p class="text-gray-400 text-sm leading-relaxed max-w-sm">
Plataforma de gestión de servicios, monitoreo de tickets y administración de usuarios. Acceso restringido únicamente a personal autorizado. Plataforma de gestión de servicios, monitoreo de tickets y administración de usuarios.
Acceso restringido únicamente a personal autorizado.
</p> </p>
</div> </div>
<div class="relative z-10 mt-12"> <div class="relative z-10 mt-12">
<div class="space-y-3"> <div class="space-y-3">
<div class="flex items-center space-x-3 text-xs text-gray-400 font-mono"> <div class="flex items-center space-x-3 text-xs text-gray-400 font-mono">
<Icon name="check-circle" className="w-4 h-4 text-green-500" /> <Icon name="check-circle" className="w-4 h-4 text-green-500" />
<span>System Status: Operational</span> <span>System Status: Operational</span>
</div> </div>
<div class="flex items-center space-x-3 text-xs text-gray-400 font-mono"> <div class="flex items-center space-x-3 text-xs text-gray-400 font-mono">
<Icon name="shield" className="w-4 h-4 text-blue-500" /> <Icon name="shield" className="w-4 h-4 text-blue-500" />
<span>256-bit Encryption Enabled</span> <span>256-bit Encryption Enabled</span>
</div> </div>
</div> </div>
</div> </div>
@@ -106,106 +112,129 @@
<!-- Right Side: Login Form --> <!-- Right Side: Login Form -->
<div class="p-8 md:p-12 flex flex-col justify-center"> <div class="p-8 md:p-12 flex flex-col justify-center">
<div class="max-w-sm mx-auto w-full">
<div class="mb-8">
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-1">Iniciar Sesión</h2>
<p class="text-sm text-gray-500 dark:text-gray-400">Acceso al sistema central</p>
</div>
<div class="max-w-sm mx-auto w-full"> <form on:submit|preventDefault={handleLogin} class="space-y-5">
<div class="mb-8"> {#if errorMessage}
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-1">Iniciar Sesión</h2> <div
<p class="text-sm text-gray-500 dark:text-gray-400">Acceso al sistema central</p> class="p-3 rounded-md bg-red-50 dark:bg-red-900/10 border border-red-200 dark:border-red-900 flex items-start gap-3"
</div> >
<Icon
name="alert-triangle"
className="w-5 h-5 text-red-600 dark:text-red-500 flex-shrink-0 mt-0.5"
/>
<p class="text-sm text-red-600 dark:text-red-500">{errorMessage}</p>
</div>
{/if}
<form on:submit|preventDefault={handleLogin} class="space-y-5"> {#if !showTwoFactor}
{#if errorMessage} <div class="space-y-4">
<div class="p-3 rounded-md bg-red-50 dark:bg-red-900/10 border border-red-200 dark:border-red-900 flex items-start gap-3"> <div>
<Icon name="alert-triangle" className="w-5 h-5 text-red-600 dark:text-red-500 flex-shrink-0 mt-0.5" /> <label
<p class="text-sm text-red-600 dark:text-red-500">{errorMessage}</p> for="email"
</div> class="block text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-1"
{/if} >Usuario / Correo</label
>
<div class="relative group">
<div
class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-gray-400 group-focus-within:text-blue-500 transition-colors"
>
<Icon name="user" className="w-5 h-5" />
</div>
<input
id="email"
type="email"
bind:value={email}
on:keydown={handleKeyDown}
class="form-input w-full pl-10 py-2.5 bg-gray-50 dark:bg-gray-800 border-gray-300 dark:border-gray-700 rounded focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all font-mono text-sm"
placeholder="admin@aduanasoft.com"
required
disabled={isLoading}
/>
</div>
</div>
{#if !showTwoFactor} <div>
<div class="space-y-4"> <label
<div> for="password"
<label for="email" class="block text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-1">Usuario / Correo</label> class="block text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-1"
<div class="relative group"> >Clave de Acceso</label
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-gray-400 group-focus-within:text-blue-500 transition-colors"> >
<Icon name="user" className="w-5 h-5" /> <div class="relative group">
</div> <div
<input class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-gray-400 group-focus-within:text-blue-500 transition-colors"
id="email" >
type="email" <Icon name="lock" className="w-5 h-5" />
bind:value={email} </div>
on:keydown={handleKeyDown} <input
class="form-input w-full pl-10 py-2.5 bg-gray-50 dark:bg-gray-800 border-gray-300 dark:border-gray-700 rounded focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all font-mono text-sm" id="password"
placeholder="admin@aduanasoft.com" type="password"
required bind:value={password}
disabled={isLoading} on:keydown={handleKeyDown}
/> class="form-input w-full pl-10 py-2.5 bg-gray-50 dark:bg-gray-800 border-gray-300 dark:border-gray-700 rounded focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all font-mono text-sm"
</div> placeholder="••••••••••••"
</div> required
disabled={isLoading}
/>
</div>
</div>
</div>
{:else}
<!-- 2FA Input -->
<div
class="bg-blue-50 dark:bg-blue-900/10 p-4 rounded-lg border border-blue-100 dark:border-blue-800/30"
>
<label
for="code"
class="block text-xs font-semibold uppercase tracking-wider text-blue-800 dark:text-blue-300 mb-2 text-center"
>Verificación de Seguridad</label
>
<div class="relative">
<input
id="code"
type="text"
bind:value={totpCode}
on:keydown={handleKeyDown}
class="form-input w-full py-3 rounded border-blue-300 dark:border-blue-700 focus:ring-blue-500 focus:border-blue-500 text-center tracking-[0.5em] font-mono text-lg bg-white dark:bg-gray-800"
placeholder="000000"
maxlength="6"
required
disabled={isLoading}
autofocus
/>
</div>
<p class="text-xs text-blue-600 dark:text-blue-400 mt-2 text-center">
Consulte su dispositivo autenticador
</p>
</div>
{/if}
<div> <div class="pt-4">
<label for="password" class="block text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-1">Clave de Acceso</label> <button
<div class="relative group"> type="submit"
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-gray-400 group-focus-within:text-blue-500 transition-colors"> class="w-full flex justify-center py-2.5 px-4 rounded bg-gray-900 dark:bg-gray-700 text-white font-medium hover:bg-gray-800 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-900 transition-colors disabled:opacity-50 disabled:cursor-not-allowed shadow-sm"
<Icon name="lock" className="w-5 h-5" /> disabled={isLoading}
</div> >
<input {#if isLoading}
id="password" <Icon name="loader" className="w-4 h-4 animate-spin mr-2" />
type="password" Autenticando...
bind:value={password} {:else}
on:keydown={handleKeyDown} {showTwoFactor ? 'Verificar Token' : 'Entrar al Panel'}
class="form-input w-full pl-10 py-2.5 bg-gray-50 dark:bg-gray-800 border-gray-300 dark:border-gray-700 rounded focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all font-mono text-sm" {/if}
placeholder="••••••••••••" </button>
required </div>
disabled={isLoading} </form>
/> </div>
</div>
</div>
</div>
{:else} <div class="mt-8 pt-6 border-t border-gray-100 dark:border-gray-800">
<!-- 2FA Input --> <p class="text-[10px] text-gray-400 text-center uppercase tracking-widest">
<div class="bg-blue-50 dark:bg-blue-900/10 p-4 rounded-lg border border-blue-100 dark:border-blue-800/30"> Aduanasoft Internal Systems © 2024
<label for="code" class="block text-xs font-semibold uppercase tracking-wider text-blue-800 dark:text-blue-300 mb-2 text-center">Verificación de Seguridad</label> </p>
<div class="relative"> </div>
<input
id="code"
type="text"
bind:value={totpCode}
on:keydown={handleKeyDown}
class="form-input w-full py-3 rounded border-blue-300 dark:border-blue-700 focus:ring-blue-500 focus:border-blue-500 text-center tracking-[0.5em] font-mono text-lg bg-white dark:bg-gray-800"
placeholder="000000"
maxlength="6"
required
disabled={isLoading}
autofocus
/>
</div>
<p class="text-xs text-blue-600 dark:text-blue-400 mt-2 text-center">
Consulte su dispositivo autenticador
</p>
</div>
{/if}
<div class="pt-4">
<button
type="submit"
class="w-full flex justify-center py-2.5 px-4 rounded bg-gray-900 dark:bg-gray-700 text-white font-medium hover:bg-gray-800 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-900 transition-colors disabled:opacity-50 disabled:cursor-not-allowed shadow-sm"
disabled={isLoading}
>
{#if isLoading}
<Icon name="loader" className="w-4 h-4 animate-spin mr-2" />
Autenticando...
{:else}
{showTwoFactor ? 'Verificar Token' : 'Entrar al Panel'}
{/if}
</button>
</div>
</form>
</div>
<div class="mt-8 pt-6 border-t border-gray-100 dark:border-gray-800">
<p class="text-[10px] text-gray-400 text-center uppercase tracking-widest">Aduanasoft Internal Systems © 2024</p>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -0,0 +1,43 @@
# Scripts PowerShell en ServiceManagerWeb
## security-test-data.ps1
- **Propósito:** Genera o limpia datos de prueba para análisis de seguridad.
- **Uso:**
- `. ools\security-test-data.ps1 generar` → Genera datos de prueba.
- `. ools\security-test-data.ps1 limpiar` → Limpia los datos de prueba.
- **Funcionamiento:** Verifica que el contenedor backend esté corriendo y ejecuta el script Python correspondiente dentro del contenedor.
## test_critical_sync.ps1
- **Propósito:** Verifica la sincronización de incidentes críticos entre Auditoría y Seguridad.
- **Pasos:**
1. Login como admin y obtiene token.
2. Consulta estadísticas del módulo Auditoría.
3. Consulta estadísticas del módulo Seguridad.
- **Resultado:** Muestra si los incidentes críticos están sincronizados.
## test_tenant_update.ps1
- **Propósito:** Prueba el endpoint de actualización de tenants.
- **Pasos:**
1. Login como admin.
2. Obtiene lista de tenants.
3. Actualiza el tenant (ejemplo: teléfono y status).
- **Resultado:** Verifica que la actualización funcione correctamente.
## test_manual.ps1
- **Propósito:** Pruebas manuales de endpoints clave.
- **Pasos:**
1. Login y obtención de token.
2. Listar categorías.
3. Crear ticket con SLA automático.
- **Resultado:** Permite validar manualmente el flujo de API.
## test_frontend_integration.ps1
- **Propósito:** Verifica la integración entre frontend y backend.
- **Pasos:**
1. Verifica servicios Docker.
2. Login y obtención de token.
3. Verifica tickets con SLA.
- **Resultado:** Confirma que el frontend puede consumir correctamente el backend.
---
**Recomendación:** Conserva estos scripts para testing e integración. Documenta cualquier script nuevo siguiendo este formato.

View File

@@ -0,0 +1,29 @@
# Guía rápida de scripts esenciales
## 1. setup-dev.sh
Configura el entorno de desarrollo completo (servicios, dependencias).
## 2. seed_data.py
Inicializa categorías, sistemas y usuarios demo.
## 3. seed_tickets.py
Genera tickets de prueba (requiere seed_data.py ejecutado).
## 4. run_tests.sh
Ejecuta la suite de tests de integración.
## 5. reset_passwords.py
Resetea contraseñas de usuarios demo para pruebas de login.
## 6. generate_sla_test_data.py
Crea tickets con diferentes estados de SLA.
## 7. generate_security_test_data.py
Genera logs de auditoría de prueba.
## 8. check_tenants.py
Lista y audita los tenants existentes.
---
**Recomendación:** Ejecuta los scripts en este orden para tener un entorno funcional y datos de prueba completos. Elimina los scripts de debugging/manuales si no los necesitas para troubleshooting avanzado.

View File

@@ -1,7 +0,0 @@
from app.models.ticket import Ticket
from app.models import relationships # ensure relationships are loaded
from sqlalchemy import inspect
mapper = inspect(Ticket)
print("Relationships:", [r.key for r in mapper.relationships])
print("Columns:", [c.key for c in mapper.columns])

View File

@@ -1,40 +0,0 @@
"""Check SLA state of tickets and categories"""
import asyncio
import os
import sys
sys.path.insert(0, '/app')
os.chdir('/app')
from sqlalchemy.ext.asyncio import create_async_engine
from sqlalchemy import text
async def run():
database_url = os.environ.get('DATABASE_URL', 'postgresql+asyncpg://postgres:postgres@db:5432/servicemanager')
engine = create_async_engine(database_url)
async with engine.connect() as c:
print("=== CATEGORIES SLA HOURS ===")
r = await c.execute(text(
"SELECT name, sla_response_hours, sla_resolution_hours "
"FROM ticket_categories "
"ORDER BY name"
))
for row in r.fetchall():
print(f" {row[0]}: response={row[1]}h, resolution={row[2]}h")
print("\n=== TICKETS SLA DATES ===")
r2 = await c.execute(text(
"SELECT ticket_number, category_id, sla_response_due, sla_resolution_due "
"FROM tickets "
"ORDER BY created_at "
"LIMIT 10"
))
for row in r2.fetchall():
print(f" {row[0]}: cat={str(row[1])[:8] if row[1] else 'None'}, sla_resp={row[2]}, sla_res={row[3]}")
await engine.dispose()
asyncio.run(run())

View File

@@ -1,41 +0,0 @@
"""Debug: check if ticket's category_id maps to a valid category and what tenant it belongs to"""
import asyncio
import os
import sys
sys.path.insert(0, '/app')
os.chdir('/app')
from sqlalchemy.ext.asyncio import create_async_engine
from sqlalchemy import text
async def run():
database_url = os.environ.get('DATABASE_URL', 'postgresql+asyncpg://postgres:postgres@db:5432/servicemanager')
engine = create_async_engine(database_url)
async with engine.connect() as c:
# Check tickets and their category names via direct JOIN
r = await c.execute(text("""
SELECT t.ticket_number, t.category_id,
cat.name as category_name, cat.tenant_id as cat_tenant,
t.tenant_id as ticket_tenant
FROM tickets t
LEFT JOIN ticket_categories cat ON cat.id = t.category_id
WHERE t.category_id IS NOT NULL
LIMIT 10
"""))
print("=== TICKET -> CATEGORY JOIN ===")
for row in r.fetchall():
match = "✓ SAME TENANT" if row[3] == row[4] else "✗ DIFFERENT TENANT"
print(f" {row[0]}: cat_id={str(row[1])[:8]}, cat_name={row[2]}, {match}")
# Check what tenant aduanasoft-demo is
r2 = await c.execute(text("SELECT id, slug FROM tenants WHERE slug='aduanasoft-demo'"))
tenant = r2.fetchone()
print(f"\nTenant aduanasoft-demo: {tenant[0] if tenant else 'NOT FOUND'}")
await engine.dispose()
asyncio.run(run())

View File

@@ -1,49 +0,0 @@
"""Debug: check SQLAlchemy ORM category loading"""
import asyncio
import os
import sys
sys.path.insert(0, '/app')
os.chdir('/app')
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker, selectinload
from sqlalchemy import select
from app.models.ticket import Ticket
from app.models.category import Category
async def run():
database_url = os.environ.get('DATABASE_URL', 'postgresql+asyncpg://postgres:postgres@db:5432/servicemanager')
engine = create_async_engine(database_url, echo=True)
async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
async with async_session() as session:
# Test selectinload
result = await session.execute(
select(Ticket)
.options(selectinload(Ticket.category))
.where(Ticket.category_id != None)
.limit(3)
)
tickets = result.scalars().all()
print(f"\n=== ORM RESULTS ({len(tickets)} tickets) ===")
for t in tickets:
print(f" {t.ticket_number}: category_id={t.category_id}, category={t.category}")
if t.category:
print(f" -> category.name={t.category.name}")
else:
print(f" -> category is None!")
# Check if Category model can be queried directly
r2 = await session.execute(select(Category).limit(3))
cats = r2.scalars().all()
print(f"\n=== DIRECT CATEGORY QUERY ({len(cats)} categories) ===")
for c in cats:
print(f" id={c.id}, name={c.name}")
await engine.dispose()
asyncio.run(run())

22
scripts/reset-fabrica.sh Normal file
View File

@@ -0,0 +1,22 @@
#!/bin/bash
# Script para resetear datos a estado de fábrica (solo datos, no afecta estructura ni funcionalidad)
set -e
echo "🧹 Reseteando datos del sistema..."
# 1. Eliminar datos de tickets, comentarios, logs, auditoría, uploads
# (Ejemplo: usando comandos SQL directos desde el contenedor)
docker-compose exec backend psql -U servicemanager -d servicemanager -c "TRUNCATE tickets, ticket_comments, audit_logs, uploads RESTART IDENTITY CASCADE;"
echo "✅ Datos eliminados."
# 2. Volver a poblar datos demo
docker-compose exec backend python scripts/seed_data.py
docker-compose exec backend python scripts/seed_tickets.py
docker-compose exec backend python scripts/generate_sla_test_data.py
docker-compose exec backend python scripts/generate_security_test_data.py
docker-compose exec backend python scripts/reset_passwords.py
echo "🎉 Sistema restaurado a estado de fábrica demo."