limpieza de scipts
This commit is contained in:
@@ -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")
|
|
||||||
@@ -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)
|
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -1,33 +1,33 @@
|
|||||||
<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 = '';
|
||||||
let totpCode = '';
|
let totpCode = '';
|
||||||
let isLoading = false;
|
let isLoading = false;
|
||||||
let showTwoFactor = false;
|
let showTwoFactor = false;
|
||||||
let errorMessage = '';
|
let errorMessage = '';
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
// Redirect if already authenticated
|
// Redirect if already authenticated
|
||||||
if ($auth.isAuthenticated) {
|
if ($auth.isAuthenticated) {
|
||||||
goto('/');
|
goto('/');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
async function handleLogin() {
|
async function handleLogin() {
|
||||||
if (!email || !password) {
|
if (!email || !password) {
|
||||||
errorMessage = 'Por favor completa todos los campos';
|
errorMessage = 'Por favor completa todos los campos';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
errorMessage = '';
|
errorMessage = '';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await auth.login({
|
await auth.login({
|
||||||
email,
|
email,
|
||||||
@@ -35,25 +35,25 @@
|
|||||||
tenant_slug: 'aduanasoft',
|
tenant_slug: 'aduanasoft',
|
||||||
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);
|
||||||
|
|
||||||
// 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 {
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleKeyDown(event: KeyboardEvent) {
|
function handleKeyDown(event: KeyboardEvent) {
|
||||||
if (event.key === 'Enter') {
|
if (event.key === 'Enter') {
|
||||||
handleLogin();
|
handleLogin();
|
||||||
@@ -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="max-w-sm mx-auto w-full">
|
<div class="mb-8">
|
||||||
<div class="mb-8">
|
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-1">Iniciar Sesión</h2>
|
||||||
<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>
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400">Acceso al sistema central</p>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<form on:submit|preventDefault={handleLogin} class="space-y-5">
|
<form on:submit|preventDefault={handleLogin} class="space-y-5">
|
||||||
{#if errorMessage}
|
{#if errorMessage}
|
||||||
<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" />
|
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"
|
||||||
<p class="text-sm text-red-600 dark:text-red-500">{errorMessage}</p>
|
>
|
||||||
</div>
|
<Icon
|
||||||
{/if}
|
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}
|
||||||
|
|
||||||
{#if !showTwoFactor}
|
{#if !showTwoFactor}
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label for="email" class="block text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-1">Usuario / Correo</label>
|
<label
|
||||||
<div class="relative group">
|
for="email"
|
||||||
<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="block text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-1"
|
||||||
<Icon name="user" className="w-5 h-5" />
|
>Usuario / Correo</label
|
||||||
</div>
|
>
|
||||||
<input
|
<div class="relative group">
|
||||||
id="email"
|
<div
|
||||||
type="email"
|
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"
|
||||||
bind:value={email}
|
>
|
||||||
on:keydown={handleKeyDown}
|
<Icon name="user" className="w-5 h-5" />
|
||||||
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="admin@aduanasoft.com"
|
<input
|
||||||
required
|
id="email"
|
||||||
disabled={isLoading}
|
type="email"
|
||||||
/>
|
bind:value={email}
|
||||||
</div>
|
on:keydown={handleKeyDown}
|
||||||
</div>
|
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>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<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>
|
<label
|
||||||
<div class="relative group">
|
for="password"
|
||||||
<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="block text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-1"
|
||||||
<Icon name="lock" className="w-5 h-5" />
|
>Clave de Acceso</label
|
||||||
</div>
|
>
|
||||||
<input
|
<div class="relative group">
|
||||||
id="password"
|
<div
|
||||||
type="password"
|
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"
|
||||||
bind:value={password}
|
>
|
||||||
on:keydown={handleKeyDown}
|
<Icon name="lock" className="w-5 h-5" />
|
||||||
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="••••••••••••"
|
<input
|
||||||
required
|
id="password"
|
||||||
disabled={isLoading}
|
type="password"
|
||||||
/>
|
bind:value={password}
|
||||||
</div>
|
on:keydown={handleKeyDown}
|
||||||
</div>
|
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="••••••••••••"
|
||||||
|
required
|
||||||
{:else}
|
disabled={isLoading}
|
||||||
<!-- 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">
|
</div>
|
||||||
<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>
|
||||||
<div class="relative">
|
</div>
|
||||||
<input
|
{:else}
|
||||||
id="code"
|
<!-- 2FA Input -->
|
||||||
type="text"
|
<div
|
||||||
bind:value={totpCode}
|
class="bg-blue-50 dark:bg-blue-900/10 p-4 rounded-lg border border-blue-100 dark:border-blue-800/30"
|
||||||
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"
|
<label
|
||||||
placeholder="000000"
|
for="code"
|
||||||
maxlength="6"
|
class="block text-xs font-semibold uppercase tracking-wider text-blue-800 dark:text-blue-300 mb-2 text-center"
|
||||||
required
|
>Verificación de Seguridad</label
|
||||||
disabled={isLoading}
|
>
|
||||||
autofocus
|
<div class="relative">
|
||||||
/>
|
<input
|
||||||
</div>
|
id="code"
|
||||||
<p class="text-xs text-blue-600 dark:text-blue-400 mt-2 text-center">
|
type="text"
|
||||||
Consulte su dispositivo autenticador
|
bind:value={totpCode}
|
||||||
</p>
|
on:keydown={handleKeyDown}
|
||||||
</div>
|
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"
|
||||||
{/if}
|
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">
|
<div class="pt-4">
|
||||||
<button
|
<button
|
||||||
type="submit"
|
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"
|
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}
|
disabled={isLoading}
|
||||||
>
|
>
|
||||||
{#if isLoading}
|
{#if isLoading}
|
||||||
<Icon name="loader" className="w-4 h-4 animate-spin mr-2" />
|
<Icon name="loader" className="w-4 h-4 animate-spin mr-2" />
|
||||||
Autenticando...
|
Autenticando...
|
||||||
{:else}
|
{:else}
|
||||||
{showTwoFactor ? 'Verificar Token' : 'Entrar al Panel'}
|
{showTwoFactor ? 'Verificar Token' : 'Entrar al Panel'}
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-8 pt-6 border-t border-gray-100 dark:border-gray-800">
|
<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>
|
<p class="text-[10px] text-gray-400 text-center uppercase tracking-widest">
|
||||||
</div>
|
Aduanasoft Internal Systems © 2024
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
43
scripts/README-powershell.md
Normal file
43
scripts/README-powershell.md
Normal 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.
|
||||||
29
scripts/README-uso-rapido.md
Normal file
29
scripts/README-uso-rapido.md
Normal 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.
|
||||||
@@ -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])
|
|
||||||
@@ -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())
|
|
||||||
@@ -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())
|
|
||||||
@@ -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
22
scripts/reset-fabrica.sh
Normal 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."
|
||||||
Reference in New Issue
Block a user