Files
HoruxDespachosNuevo/apps/api/src/controllers/fiel.controller.ts

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