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
});
toast.success('¡Bienvenido! Has iniciado sesión correctamente');
toast.success('¡Bienvenido! Has iniciado sesión correctamente');
goto('/');
} catch (error: any) {
console.error('Login error:', error);
@@ -46,9 +46,9 @@
// Check if 2FA is required
if (error.message.includes('two-factor') || error.message.includes('2FA')) {
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 {
errorMessage = error.message || 'Error al iniciar sesión';
errorMessage = error.message || 'Error al iniciar sesión';
toast.error(errorMessage);
}
} finally {
@@ -96,8 +96,8 @@
de Servicios de TI
</h2>
<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
reciba asistencia especializada para garantizar la continuidad de su operación.
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.
</p>
</div>
@@ -135,7 +135,7 @@
<!-- Email Input -->
<div class="space-y-1.5">
<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="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
@@ -160,7 +160,7 @@
<!-- Password Input -->
<div class="space-y-1.5">
<label for="password" class="block text-sm font-semibold text-gray-700"
>Contraseña</label
>Contraseña</label
>
<div class="relative group">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
@@ -176,7 +176,7 @@
bind:value={password}
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"
placeholder="••••••••"
placeholder="••••••••"
required
disabled={isLoading}
/>
@@ -187,7 +187,7 @@
bind:value={password}
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"
placeholder="••••••••"
placeholder="••••••••"
required
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"
on:click={() => goto('/forgot-password')}
>
Olvidé mi clave
Olvidé mi clave
</button>
</div>
</div>
@@ -229,9 +229,9 @@
<!-- 2FA Input -->
<div class="space-y-4 animate-slide-up">
<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="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
@@ -268,7 +268,7 @@
</div>
<div class="mt-8 text-center text-xs text-gray-400">
© 2026 Aduanasoft. Acceso exclusivo autorizado.
© 2026 Aduanasoft. Acceso exclusivo autorizado.
</div>
</form>
</div>

View File

@@ -1,9 +1,9 @@
<script lang="ts">
import { goto } from '$app/navigation';
import Icon from '$lib/components/Icon.svelte';
import { auth } from '$lib/stores/auth.js';
import { toast } from '$lib/stores/toast.js';
import { goto } from '$app/navigation';
import { onMount } from 'svelte';
import Icon from '$lib/components/Icon.svelte';
let email = '';
let password = '';
@@ -36,7 +36,7 @@
totp_code: totpCode || undefined
});
toast.success('¡Bienvenido! Has iniciado sesión correctamente');
toast.success('¡Bienvenido! Has iniciado sesión correctamente');
goto('/');
} catch (error: any) {
console.error('Login error:', error);
@@ -44,9 +44,9 @@
// Check if 2FA is required
if (error.message.includes('two-factor') || error.message.includes('2FA')) {
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 {
errorMessage = error.message || 'Error al iniciar sesión';
errorMessage = error.message || 'Error al iniciar sesión';
toast.error(errorMessage);
}
} finally {
@@ -61,18 +61,25 @@
}
</script>
<svelte:head>
<title>Acceso Admin - ServiceManager</title>
</svelte:head>
<div 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">
<div
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 -->
<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 -->
<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="flex items-center space-x-3 mb-6">
@@ -82,11 +89,10 @@
<span class="text-sm font-mono tracking-wider text-blue-400">INTERNAL_ACCESS_V2</span>
</div>
<h1 class="text-3xl font-bold tracking-tight mb-4">
Panel de Administración
</h1>
<h1 class="text-3xl font-bold tracking-tight mb-4">Panel de Administración</h1>
<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>
</div>
@@ -106,17 +112,21 @@
<!-- Right Side: Login Form -->
<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>
<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>
<form on:submit|preventDefault={handleLogin} class="space-y-5">
{#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">
<Icon name="alert-triangle" className="w-5 h-5 text-red-600 dark:text-red-500 flex-shrink-0 mt-0.5" />
<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"
>
<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}
@@ -124,9 +134,15 @@
{#if !showTwoFactor}
<div class="space-y-4">
<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
for="email"
class="block text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-1"
>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">
<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
@@ -143,9 +159,15 @@
</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
for="password"
class="block text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-1"
>Clave de Acceso</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">
<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="lock" className="w-5 h-5" />
</div>
<input
@@ -154,18 +176,23 @@
bind:value={password}
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="••••••••••••"
placeholder="••••••••••••"
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="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"
@@ -204,7 +231,9 @@
</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>
<p class="text-[10px] text-gray-400 text-center uppercase tracking-widest">
Aduanasoft Internal Systems © 2024
</p>
</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."