Initial commit - Horux Despachos NL
This commit is contained in:
136
apps/api/src/controllers/fiel.controller.ts
Normal file
136
apps/api/src/controllers/fiel.controller.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import type { Request, Response } from 'express';
|
||||
import { uploadFiel, getFielStatus, deleteFiel } from '../services/fiel.service.js';
|
||||
import type { FielUploadRequest } from '@horux/shared';
|
||||
import type { Pool } from 'pg';
|
||||
|
||||
/**
|
||||
* Crea recordatorios automáticos de vencimiento de e.firma en el calendario.
|
||||
* 60 días, 30 días y 7 días antes del vencimiento.
|
||||
* Elimina recordatorios previos de e.firma antes de crear nuevos.
|
||||
*/
|
||||
async function crearRecordatoriosEfirma(
|
||||
pool: Pool,
|
||||
userId: string,
|
||||
validUntil: string,
|
||||
): Promise<void> {
|
||||
const vencimiento = new Date(validUntil);
|
||||
const PREFIJO = '[e.firma]';
|
||||
|
||||
// Eliminar recordatorios previos de e.firma para evitar duplicados al re-subir
|
||||
await pool.query(
|
||||
`DELETE FROM recordatorios WHERE titulo LIKE $1`,
|
||||
[`${PREFIJO}%`]
|
||||
);
|
||||
|
||||
const recordatorios = [
|
||||
{ dias: 60, titulo: `${PREFIJO} Tu e.firma vence en 60 días` },
|
||||
{ dias: 30, titulo: `${PREFIJO} Tu e.firma vence en 30 días` },
|
||||
{ dias: 7, titulo: `${PREFIJO} Tu e.firma vence en 7 días — ¡Renueva pronto!` },
|
||||
];
|
||||
|
||||
for (const { dias, titulo } of recordatorios) {
|
||||
const fecha = new Date(vencimiento);
|
||||
fecha.setDate(fecha.getDate() - dias);
|
||||
|
||||
// Solo crear si la fecha no ha pasado
|
||||
if (fecha > new Date()) {
|
||||
await pool.query(
|
||||
`INSERT INTO recordatorios (titulo, descripcion, fecha_limite, privado, creado_por)
|
||||
VALUES ($1, $2, $3, false, $4)`,
|
||||
[
|
||||
titulo,
|
||||
`La e.firma (FIEL) vence el ${vencimiento.toLocaleDateString('es-MX')}. Renueva en el portal del SAT.`,
|
||||
fecha.toISOString().split('T')[0],
|
||||
userId,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function effectiveTenantId(req: Request): string {
|
||||
return req.viewingTenantId || req.user!.tenantId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sube y configura las credenciales FIEL
|
||||
*/
|
||||
export async function upload(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const tenantId = effectiveTenantId(req);
|
||||
|
||||
const { cerFile, keyFile, password } = req.body as FielUploadRequest;
|
||||
|
||||
if (!cerFile || !keyFile || !password) {
|
||||
res.status(400).json({ error: 'cerFile, keyFile y password son requeridos' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate file sizes (typical .cer/.key files are under 10KB, base64 ~33% larger)
|
||||
const MAX_FILE_SIZE = 50_000; // 50KB base64 ≈ ~37KB binary
|
||||
if (cerFile.length > MAX_FILE_SIZE || keyFile.length > MAX_FILE_SIZE) {
|
||||
res.status(400).json({ error: 'Los archivos FIEL son demasiado grandes (máx 50KB)' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (password.length > 256) {
|
||||
res.status(400).json({ error: 'Contraseña FIEL demasiado larga' });
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await uploadFiel(tenantId, cerFile, keyFile, password);
|
||||
|
||||
if (!result.success) {
|
||||
res.status(400).json({ error: result.message });
|
||||
return;
|
||||
}
|
||||
|
||||
// Crear recordatorios de vencimiento en el calendario
|
||||
if (result.status?.validUntil && req.tenantPool) {
|
||||
crearRecordatoriosEfirma(req.tenantPool, req.user!.userId, result.status.validUntil)
|
||||
.catch(err => console.error('[FIEL] Error creando recordatorios de vencimiento:', err));
|
||||
}
|
||||
|
||||
res.json({
|
||||
message: result.message,
|
||||
status: result.status,
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error('[FIEL Controller] Error en upload:', error);
|
||||
res.status(500).json({ error: 'Error interno del servidor' });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene el estado de la FIEL configurada
|
||||
*/
|
||||
export async function status(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const tenantId = effectiveTenantId(req);
|
||||
const fielStatus = await getFielStatus(tenantId);
|
||||
res.json(fielStatus);
|
||||
} catch (error: any) {
|
||||
console.error('[FIEL Controller] Error en status:', error);
|
||||
res.status(500).json({ error: 'Error interno del servidor' });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Elimina las credenciales FIEL
|
||||
*/
|
||||
export async function remove(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const tenantId = effectiveTenantId(req);
|
||||
const deleted = await deleteFiel(tenantId);
|
||||
|
||||
if (!deleted) {
|
||||
res.status(404).json({ error: 'No hay FIEL configurada' });
|
||||
return;
|
||||
}
|
||||
|
||||
res.json({ message: 'FIEL eliminada correctamente' });
|
||||
} catch (error: any) {
|
||||
console.error('[FIEL Controller] Error en remove:', error);
|
||||
res.status(500).json({ error: 'Error interno del servidor' });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user