137 lines
4.3 KiB
TypeScript
137 lines
4.3 KiB
TypeScript
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' });
|
|
}
|
|
}
|