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 { 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 { 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 { 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 { 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' }); } }