Saltearse al contenido

API de Gestión Financiera

API de Gestión Financiera - Sistema Empresarial

Sección titulada «API de Gestión Financiera - Sistema Empresarial»

API de Gestión Financiera es una API REST completa y lista para producción diseñada para control financiero empresarial, demostrando características avanzadas de Veloce-TS con lógica de negocio del mundo real. Este ejemplo muestra cómo construir un sistema financiero robusto con autenticación, caché, trazabilidad de auditoría y gestión automática de inventario.

:::tip Listo para Producción Este ejemplo está diseñado para ser listo para producción e incluye todas las características que necesitarías en una aplicación empresarial real: caché, logging, monitoreo y manejo integral de errores. :::

  • 7 Controladores REST (Auth, Ingresos, Gastos, Materiales, Empleados, Cheques, Notificaciones)
  • Routing basado en decoradores con @Controller, @Get, @Post, etc.
  • Validación automática con esquemas Zod
  • Documentación OpenAPI/Swagger auto-generada
  • Serialización de Request/Response (manejo de BigInt)
  • Manejo de errores y estrategias de recuperación
  • Autenticación JWT con expiración configurable
  • Hash de contraseñas usando bcrypt
  • Rutas protegidas con middleware de autenticación manual
  • Control basado en roles (extensible)
  • Validación de tokens y manejo de errores
  • PostgreSQL como base de datos
  • Prisma ORM para acceso type-safe a la base de datos
  • Relaciones complejas entre entidades
  • Agregaciones y consultas estadísticas
  • Soporte de transacciones para integridad de datos
  • Caché Redis con configuración de TTL
  • Invalidación inteligente de caché en cambios de datos
  • Patrón Wrap para implementación limpia de caché
  • Endpoint de monitoreo de caché para debugging
  • Gestión auto-inventario (gastos crean materiales automáticamente)
  • Resúmenes financieros con agregaciones
  • Seguimiento de datos históricos
  • Registro de auditoría para todas las operaciones
  • Alertas de stock bajo para materiales
  • Logging estructurado con Winston
  • Health checks (base de datos, Redis, memoria)
  • Graceful shutdown (cierre elegante)
  • Docker Compose para desarrollo fácil
  • Configuración por variables de entorno
  • Endpoints de monitoreo para Redis y estado del sistema
financial-api/
├── src/
│ ├── controllers/ # 7 controladores REST
│ │ ├── auth.controller.ts # Autenticación y perfil
│ │ ├── income.controller.ts # Gestión de ingresos
│ │ ├── expense.controller.ts # Gastos + auto-creación de materiales
│ │ ├── material.controller.ts # Gestión de inventario
│ │ ├── employee.controller.ts # Registros de empleados
│ │ ├── check.controller.ts # Gestión de cheques
│ │ └── notification.controller.ts # Notificaciones
│ ├── schemas/ # Esquemas de validación Zod
│ ├── config/ # Configuración
│ │ ├── prisma.ts # Cliente Prisma
│ │ └── redis.ts # Cliente Redis
│ ├── utils/ # Utilidades
│ │ ├── cache.ts # Servicio de caché
│ │ ├── logger.ts # Logger Winston
│ │ └── serializer.ts # Serialización de datos
│ ├── middleware/ # Middleware personalizado
│ │ └── auth.ts # Helper de autenticación
│ └── index.ts # Punto de entrada
├── prisma/
│ └── schema.prisma # Esquema de base de datos
├── scripts/
│ └── create-admin-user.ts # Script de configuración admin
├── docker-compose.yml # PostgreSQL + Redis
└── public/
└── docs.html # Swagger UI personalizado

Este ejemplo es perfecto para:

  • Sistemas de gestión financiera
  • Aplicaciones de control de inventario
  • Plataformas de gestión empresarial
  • Software de contabilidad
  • Sistemas ERP
  • Node.js >= 18.0.0 o Bun >= 1.0.0
  • Docker y Docker Compose
  1. Clonar el repositorio:
Ventana de terminal
git clone https://github.com/veloce-ts/examples
cd examples/financial-management-api
  1. Instalar dependencias:
Ventana de terminal
npm install
# o
bun install
  1. Configurar entorno:
Ventana de terminal
cp .env.example .env

Editar .env:

# Base de datos
DATABASE_URL="postgresql://garcia_user:garcia_password_2024@localhost:5432/garcia_renovacion"
# Redis
REDIS_HOST="localhost"
REDIS_PORT=6379
REDIS_PASSWORD="garcia_redis_2024"
# JWT
JWT_SECRET="tu-clave-super-secreta-cambiar-en-produccion"
JWT_EXPIRES_IN="24h"
# CORS
CORS_ORIGIN="http://localhost:5173"
  1. Iniciar servicios (PostgreSQL + Redis):
Ventana de terminal
docker-compose up -d
  1. Ejecutar migraciones:
Ventana de terminal
npx prisma migrate dev
npx prisma generate
  1. Crear usuario admin:
Ventana de terminal
npm run create-admin
  1. Iniciar servidor de desarrollo:
Ventana de terminal
npm run dev
# o
bun run dev
@Controller('/api/auth')
export class AuthController {
@Post('/login')
async login(@Body(LoginSchema) body: LoginRequest) {
// Buscar usuario
const user = await prisma.user.findUnique({
where: { username: body.username }
});
if (!user || !user.isActive) {
throw new Error('Credenciales inválidas');
}
// Verificar contraseña
const validPassword = await bcrypt.compare(
body.password,
user.passwordHash
);
if (!validPassword) {
throw new Error('Credenciales inválidas');
}
// Generar JWT
const token = jwt.sign(
{
id: user.id,
username: user.username,
role: user.role
},
process.env.JWT_SECRET!,
{ expiresIn: process.env.JWT_EXPIRES_IN || '24h' }
);
return {
success: true,
data: { user, token }
};
}
}
export const CreateExpenseSchema = z.object({
concept: z.string().min(1, 'El concepto es requerido'),
amount: z.number().positive('El monto debe ser positivo'),
category: z.string().min(1, 'La categoría es requerida'),
paymentMethod: z.string().min(1, 'El método de pago es requerido'),
transactionDate: z.string()
.refine(val => !isNaN(Date.parse(val)), 'Fecha inválida')
.transform(val => new Date(val)),
// Campos de compra de material
isMaterialPurchase: z.boolean().default(false),
materialName: z.string().optional(),
materialQuantity: z.number().positive().optional(),
materialUnit: z.string().optional()
}).refine((data) => {
// Validación condicional
if (data.isMaterialPurchase) {
return data.materialName && data.materialQuantity && data.materialUnit;
}
return true;
}, {
message: 'Los campos de material son requeridos cuando isMaterialPurchase es true',
path: ['materialName']
});
@Get('/stats')
async getMaterialStats(@Header('authorization') authHeader?: string) {
const user = await authenticateUser(authHeader);
const cacheKey = cacheService.generateKey('material', 'stats');
const stats = await cacheService.wrap(
cacheKey,
async () => {
// Operación costosa de base de datos
const [total, active, inactive, aggregates, materials] =
await Promise.all([
prisma.material.count(),
prisma.material.count({ where: { status: 'activo' } }),
prisma.material.count({ where: { status: 'inactivo' } }),
prisma.material.aggregate({
_sum: { currentQuantity: true, unitCost: true },
where: { status: 'activo' }
}),
prisma.material.findMany({
where: { status: 'activo' },
select: { currentQuantity: true, unitCost: true }
})
]);
// Calcular valor total del inventario
const totalValue = materials.reduce((sum, m) => {
return sum + (Number(m.currentQuantity) * Number(m.unitCost || 0));
}, 0);
return {
total,
active,
inactive,
totalQuantity: Number(aggregates._sum.currentQuantity) || 0,
totalValue,
lowStock: await prisma.material.count({
where: {
status: 'activo',
currentQuantity: { lt: 10 }
}
})
};
},
120 // Cachear por 2 minutos
);
return { success: true, data: stats };
}

Esto demuestra lógica de negocio compleja:

@Post('/')
async createExpense(
@Body(CreateExpenseSchema) body: CreateExpenseRequest,
@Header('authorization') authHeader?: string
) {
const user = await authenticateUser(authHeader);
let autoCreatedMaterialId: number | undefined;
// Auto-crear o actualizar material si es compra de material
if (body.isMaterialPurchase && body.materialName && body.materialQuantity) {
// Verificar si el material existe
let material = await prisma.material.findFirst({
where: { name: body.materialName }
});
if (material) {
// Actualizar cantidad de material existente
const updated = await prisma.material.update({
where: { id: material.id },
data: {
currentQuantity: Number(material.currentQuantity) +
Number(body.materialQuantity),
unitCost: body.amount // Actualizar precio
}
});
autoCreatedMaterialId = updated.id;
logger.info('Material actualizado desde gasto', {
materialId: updated.id,
cantidadAgregada: body.materialQuantity
});
} else {
// Crear nuevo material
const newMaterial = await prisma.material.create({
data: {
code: body.materialCode || `MAT-${Date.now()}`,
name: body.materialName,
currentQuantity: body.materialQuantity,
unitCost: body.amount,
unit: body.materialUnit || 'unidad',
minQuantity: 0,
status: 'activo',
userId: user.id
}
});
autoCreatedMaterialId = newMaterial.id;
logger.info('Material auto-creado desde gasto', {
materialId: newMaterial.id,
nombre: newMaterial.name
});
}
}
// Crear gasto con referencia al material
const expense = await prisma.expense.create({
data: {
...body,
userId: user.id,
autoCreatedMaterialId
}
});
// Registro de auditoría
await prisma.auditLog.create({
data: {
tableName: 'expenses',
recordId: expense.id,
action: 'CREATE',
newData: expense,
userId: user.id
}
});
return { success: true, data: serializeData(expense) };
}
  • POST /api/auth/login - Login de usuario
  • POST /api/auth/logout - Logout de usuario
  • GET /api/auth/profile - Obtener usuario actual
  • PUT /api/auth/profile - Actualizar perfil
  • POST /api/auth/change-password - Cambiar contraseña
  • GET /api/income - Listar ingresos (paginado)
  • GET /api/income/summary - Resumen financiero (total, count, promedio)
  • GET /api/income/history?months=6 - Datos históricos
  • POST /api/income - Crear ingreso
  • PUT /api/income/:id - Actualizar ingreso
  • DELETE /api/income/:id - Eliminar ingreso
  • GET /api/expenses - Listar gastos (paginado)
  • GET /api/expenses/summary - Resumen de gastos con categorías principales
  • POST /api/expenses - Crear gasto (auto-crea material si es necesario)
  • PUT /api/expenses/:id - Actualizar gasto
  • DELETE /api/expenses/:id - Eliminar gasto
  • GET /api/materials - Listar materiales (con filtros)
  • GET /api/materials/stats - Estadísticas de inventario (cacheado)
  • GET /api/materials/low-stock - Materiales bajo cantidad mínima
  • POST /api/materials - Crear material
  • PUT /api/materials/:id - Actualizar material
  • DELETE /api/materials/:id - Eliminar material
  • GET /api/health - Health check (base de datos, Redis, memoria)
  • GET /api/debug/redis - Monitoreo de Redis
  • GET /docs - Documentación Veloce-TS generada
  • GET /api/docs - Swagger UI personalizado
  • GET /openapi.json - Especificación OpenAPI
Ventana de terminal
# 1. Login
TOKEN=$(curl -s -X POST http://localhost:3000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin123"}' \
| jq -r '.data.token')
# 2. Obtener resumen de ingresos
curl -H "Authorization: Bearer $TOKEN" \
http://localhost:3000/api/income/summary
# 3. Crear gasto con creación automática de material
curl -X POST http://localhost:3000/api/expenses \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"amount": 5000,
"concept": "Compra de Cemento Portland",
"category": "materiales",
"supplier": "Ferretería ACME",
"paymentMethod": "efectivo",
"transactionDate": "2024-11-01",
"isMaterialPurchase": true,
"materialName": "Cemento Portland",
"materialQuantity": 50,
"materialUnit": "bolsas",
"materialCode": "MAT-CEM-001"
}'
# 4. Verificar que el material fue creado
curl -H "Authorization: Bearer $TOKEN" \
"http://localhost:3000/api/materials?searchTerm=Portland"
  1. Abrir http://localhost:3000/api/docs
  2. Click en botón “Authorize”
  3. Login a través del endpoint /api/auth/login
  4. Copiar el token de la respuesta
  5. Pegarlo como: Bearer TU_TOKEN_AQUI
  6. ¡Ahora puedes probar todos los endpoints interactivamente!

Visita http://localhost:3000/api/debug/redis para ver:

  • Total de claves en caché
  • Todas las claves de caché
  • Estadísticas de conexión
  • Datos de muestra del caché
  • Patrones de caché utilizados

Los logs se almacenan en el directorio logs/:

  • combined.log - Todos los logs de aplicación
  • error.log - Solo errores
  • exceptions.log - Excepciones no capturadas
  • rejections.log - Rechazos de promesas no manejados
# Configuración de Producción
NODE_ENV=production
PORT=3000
# Base de datos (usar credenciales de producción)
DATABASE_URL=postgresql://user:pass@prod-db:5432/dbname
# Redis (usar credenciales de producción)
REDIS_HOST=prod-redis
REDIS_PORT=6379
REDIS_PASSWORD=tu-contraseña-redis-segura
# JWT (¡CAMBIAR ESTOS!)
JWT_SECRET=tu-clave-super-secreta-de-produccion
JWT_EXPIRES_IN=24h
# CORS
CORS_ORIGIN=https://tu-dominio-produccion.com
FROM oven/bun:1 as base
WORKDIR /app
# Dependencias
COPY package*.json bun.lockb ./
RUN bun install --frozen-lockfile --production
# Código fuente
COPY . .
# Prisma
RUN bunx prisma generate
# Build
RUN bun run build
# Imagen de producción
FROM oven/bun:1-slim
WORKDIR /app
COPY --from=base /app/dist ./dist
COPY --from=base /app/node_modules ./node_modules
COPY --from=base /app/prisma ./prisma
COPY --from=base /app/package.json ./
EXPOSE 3000
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s \
CMD curl -f http://localhost:3000/api/health || exit 1
CMD ["bun", "run", "start"]
  • Estadísticas de materiales: TTL de 2 minutos
  • Invalidación de caché en crear/actualizar/eliminar
  • Invalidación basada en patrones para datos relacionados
  • Índices en campos consultados frecuentemente
  • Recuperación selectiva de campos con select
  • Connection pooling configurado
  • Consultas de agregación optimizadas

Después de estudiar este ejemplo, comprenderás:

  1. ✅ Construir APIs listas para producción con Veloce-TS
  2. ✅ Implementar autenticación JWT
  3. ✅ Diseño de base de datos y uso de Prisma ORM
  4. ✅ Estrategias de caché con Redis
  5. ✅ Implementación de lógica de negocio compleja
  6. ✅ Logging estructurado y monitoreo
  7. ✅ Manejo y recuperación de errores
  8. ✅ Documentación de API con OpenAPI
  9. ✅ Containerización con Docker
  10. ✅ Estrategias de despliegue en producción

¿Encontraste un bug o quieres mejorar este ejemplo?

Licencia MIT - ¡Libre para usar y modificar en tus proyectos!


:::tip ¿Listo para Construir? Este ejemplo proporciona una base sólida para construir sistemas financieros empresariales. ¡Haz un fork, personalízalo y hazlo tuyo! :::