# Base de Datos MySQL
DB_DRIVER=mysql
DB_HOST=localhost
DB_PORT=3306
DB_USERNAME=tu_usuario
DB_PASSWORD=tu_password
DB_DATABASE=SDC_SHORT_LINK
DB_DEBUG=false
DB_SYNC=falseDB_SYNC debe ser false en producción. Usar migraciones para cambios en esquema.
# Ejecutar todas las migraciones pendientes
npm run migration:run
# Revertir última migración
npm run migration:revert# TypeORM detecta cambios en entidades y genera migración
# El path se especifica como argumento posicional al final
npm run migration:generate ./src/migration/NombreDeLaMigracion# Crear archivo de migración vacío
# El path se especifica como argumento posicional
npm run migration:create ./src/migration/NombreDeLaMigracionsrc/
├── entity/
│ └── ShortLink.entity.ts # Entidad con decoradores TypeORM
├── repositories/
│ └── ShortLink.repository.ts # Lógica de acceso a datos
├── migration/
│ └── 1732000000000-CreateShortLinksTable.ts # Migración inicial
├── data-source.ts # Configuración DataSource
└── index.ts # Bootstrap con inicialización de BD
@Entity('short_links') // Nombre de tabla
@Index('idx_codigo', ['codigo']) // Índice para búsquedas rápidas
@PrimaryGeneratedColumn('uuid') // ID autogenerado UUID
@Column({ type: 'varchar', length: 100 }) // Columna de texto
@Column({ type: 'enum', enum: ShortLinkStatus }) // Enum
@Column({ type: 'json' }) // Columna JSON (etiquetas, historial)
@CreateDateColumn() // Auto-timestamp creación
@UpdateDateColumn() // Auto-timestamp actualización| Propiedad TypeScript | Columna MySQL |
|---|---|
urlCorta |
url_corta |
urlDestino |
url_destino |
fechaCreacion |
fecha_creacion |
fechaUltimaActualizacion |
fecha_ultima_actualizacion |
creadoPor |
creado_por |
actualizadoPor |
actualizado_por |
historialCambios |
historial_cambios |
totalClicks |
total_clicks |
fechaUltimoClick |
fecha_ultimo_click |
// Crear
const link = await shortLinkRepository.create({
codigo: 'promo2025',
urlCorta: 'https://lnk.example.com/promo2025',
proyecto: 'Marketing',
creadoPor: 'admin'
});
// Buscar por ID
const link = await shortLinkRepository.findById('uuid-aqui');
// Buscar por código
const link = await shortLinkRepository.findByCodigo('promo2025');
// Actualizar
const updated = await shortLinkRepository.update('uuid-aqui', {
urlDestino: 'https://nueva-url.com',
estado: ShortLinkStatus.ACTIVO
});
// Eliminar
await shortLinkRepository.delete('uuid-aqui');// Listar con filtros
const links = await shortLinkRepository.findAll({
proyecto: 'Marketing',
estado: ShortLinkStatus.ACTIVO
});
// Buscar por proyecto
const links = await shortLinkRepository.findByProyecto('Marketing');
// Buscar por estado
const links = await shortLinkRepository.findByEstado(ShortLinkStatus.RESERVADO_SIN_DESTINO);
// Buscar por etiqueta
const links = await shortLinkRepository.findByEtiqueta('promo');
// Búsqueda flexible
const links = await shortLinkRepository.search('navidad');// Incrementar clics (operación atómica)
await shortLinkRepository.incrementClicks('uuid-aqui');
// Enlaces más populares
const top10 = await shortLinkRepository.findMostPopular(10);
// Contar por estado
const count = await shortLinkRepository.countByEstado(ShortLinkStatus.ACTIVO);
// Contar por proyecto
const count = await shortLinkRepository.countByProyecto('Marketing');
// Total de clics
const totalClicks = await shortLinkRepository.getTotalClicks();// Verificar si código existe
const exists = await shortLinkRepository.existsByCodigo('promo2025');
// Enlaces reservados sin destino
const reservados = await shortLinkRepository.findReservados();Crea la tabla short_links con:
- ✅ 15 columnas con tipos apropiados
- ✅ Índices en
codigo,proyecto,estado,fecha_creacion - ✅ Enums para estado
- ✅ JSON para etiquetas e historial
- ✅ Timestamps automáticos
# Desarrollo
npm run migration:run
# Producción (después de compilar)
npm run build
npm run migration:run:prod# Conectar a MySQL
mysql -h localhost -u sdc -p SDC_SHORT_LINK
# Ver tablas
SHOW TABLES;
# Ver estructura de short_links
DESCRIBE short_links;
# Ver índices
SHOW INDEX FROM short_links;
# Ver migraciones ejecutadas
SELECT * FROM migrations;curl -X POST http://localhost:4300/v1/short-links \
-H "X-API-Key: dev-key-123" \
-H "Content-Type: application/json" \
-d '{
"proyecto": "Test",
"creadoPor": "admin",
"urlDestino": "https://google.com"
}'-- Ver todos los enlaces
SELECT * FROM short_links;
-- Ver enlaces activos
SELECT codigo, url_corta, url_destino, total_clicks
FROM short_links
WHERE estado = 'ACTIVO';
-- Ver enlaces por proyecto
SELECT * FROM short_links WHERE proyecto = 'Marketing';
-- Ver estadísticas
SELECT
proyecto,
COUNT(*) as total_enlaces,
SUM(total_clicks) as total_clics
FROM short_links
GROUP BY proyecto;Causa: MySQL no está corriendo o credenciales incorrectas
Solución:
# Verificar que MySQL esté corriendo
# Windows:
Get-Service mysql* | Select Name, Status
# Linux:
sudo systemctl status mysql
# Verificar conexión
mysql -h localhost -u sdc -pCausa: Migraciones no ejecutadas
Solución:
npm run migration:runCausa: Dependencias no instaladas
Solución:
npm installCausa: Código ya existe en BD
Solución: El controlador maneja esto automáticamente generando un código diferente. Si persiste, verificar lógica en resolveShortCode().
| Columna | Tipo | Null | Key | Default | Comentario |
|---|---|---|---|---|---|
| id | VARCHAR(36) | NO | PRI | UUID() | ID único |
| codigo | VARCHAR(100) | NO | UNI | Código corto | |
| url_corta | VARCHAR(255) | NO | URL completa | ||
| url_destino | TEXT | YES | NULL | URL destino | |
| estado | ENUM | NO | MUL | RESERVADO_SIN_DESTINO | Estado |
| proyecto | VARCHAR(100) | NO | MUL | Proyecto | |
| modulo | VARCHAR(100) | YES | NULL | Módulo | |
| etiquetas | JSON | YES | NULL | Etiquetas | |
| fecha_creacion | TIMESTAMP | NO | MUL | CURRENT_TIMESTAMP | Creado |
| fecha_ultima_actualizacion | TIMESTAMP | NO | CURRENT_TIMESTAMP | Actualizado | |
| creado_por | VARCHAR(100) | NO | Usuario creador | ||
| actualizado_por | VARCHAR(100) | NO | Usuario actualizador | ||
| historial_cambios | JSON | NO | '[]' | Historial | |
| total_clicks | INT UNSIGNED | NO | 0 | Contador | |
| fecha_ultimo_click | TIMESTAMP | YES | NULL | Último clic |
PRIMARY KEY (id)UNIQUE KEY (codigo)INDEX (proyecto)INDEX (estado)INDEX (fecha_creacion)
// ❌ NUNCA
synchronize: true // Puede perder datos
// ✅ SIEMPRE
synchronize: false // Usar migracionesawait appDataSource.transaction(async (manager) => {
await manager.save(link1);
await manager.save(link2);
// Si algo falla, todo se revierte
});// Validar unicidad antes de crear
const exists = await shortLinkRepository.existsByCodigo(codigo);
if (exists) {
throw new Error('Código ya existe');
}const links = await shortLinkRepository
.getRepository()
.createQueryBuilder('link')
.where('link.proyecto = :proyecto', { proyecto: 'Marketing' })
.andWhere('link.totalClicks > :minClicks', { minClicks: 100 })
.orderBy('link.totalClicks', 'DESC')
.take(10)
.getMany();// Ya configurados en migración:
// - codigo (búsqueda por código)
// - proyecto (filtrado por proyecto)
// - estado (filtrado por estado)
// - fecha_creacion (ordenamiento)Última actualización: Noviembre 2025
Autor: SimpleData Corp - Dey Gordillo