import React, { useState, useEffect, useCallback } from 'react'; import { Box, Typography, Button, Paper, CircularProgress, Alert, Select, MenuItem, FormControl, InputLabel, Table, TableContainer, TableHead, TableRow, TableCell, TableBody, Chip, IconButton, Menu, ListItemIcon, ListItemText } from '@mui/material'; import PlayCircleOutlineIcon from '@mui/icons-material/PlayCircleOutline'; import DownloadIcon from '@mui/icons-material/Download'; import MoreVertIcon from '@mui/icons-material/MoreVert'; import PaymentIcon from '@mui/icons-material/Payment'; import EmailIcon from '@mui/icons-material/Email'; import UploadFileIcon from '@mui/icons-material/UploadFile'; import { styled } from '@mui/material/styles'; import facturacionService from '../../services/Suscripciones/facturacionService'; import { usePermissions } from '../../hooks/usePermissions'; import axios from 'axios'; import type { FacturaDto } from '../../models/dtos/Suscripciones/FacturaDto'; import PagoManualModal from '../../components/Modals/Suscripciones/PagoManualModal'; import type { CreatePagoDto } from '../../models/dtos/Suscripciones/CreatePagoDto'; const anios = Array.from({ length: 10 }, (_, i) => new Date().getFullYear() - i); const meses = [ { value: 1, label: 'Enero' }, { value: 2, label: 'Febrero' }, { value: 3, label: 'Marzo' }, { value: 4, label: 'Abril' }, { value: 5, label: 'Mayo' }, { value: 6, label: 'Junio' }, { value: 7, label: 'Julio' }, { value: 8, label: 'Agosto' }, { value: 9, label: 'Septiembre' }, { value: 10, label: 'Octubre' }, { value: 11, label: 'Noviembre' }, { value: 12, label: 'Diciembre' } ]; const VisuallyHiddenInput = styled('input')({ clip: 'rect(0 0 0 0)', clipPath: 'inset(50%)', height: 1, overflow: 'hidden', position: 'absolute', bottom: 0, left: 0, whiteSpace: 'nowrap', width: 1, }); const FacturacionPage: React.FC = () => { const [selectedAnio, setSelectedAnio] = useState(new Date().getFullYear()); const [selectedMes, setSelectedMes] = useState(new Date().getMonth() + 1); const [loading, setLoading] = useState(false); const [loadingArchivo, setLoadingArchivo] = useState(false); const [loadingProceso, setLoadingProceso] = useState(false); const [apiMessage, setApiMessage] = useState(null); const [apiError, setApiError] = useState(null); const [facturas, setFacturas] = useState([]); const { tienePermiso, isSuperAdmin } = usePermissions(); const puedeGenerarFacturacion = isSuperAdmin || tienePermiso("SU006"); const puedeGenerarArchivo = isSuperAdmin || tienePermiso("SU007"); const puedeRegistrarPago = isSuperAdmin || tienePermiso("SU008"); const puedeEnviarEmail = isSuperAdmin || tienePermiso("SU009"); const [pagoModalOpen, setPagoModalOpen] = useState(false); const [selectedFactura, setSelectedFactura] = useState(null); const [anchorEl, setAnchorEl] = useState(null); const [archivoSeleccionado, setArchivoSeleccionado] = useState(null); const cargarFacturasDelPeriodo = useCallback(async () => { if (!puedeGenerarFacturacion) return; setLoading(true); try { const data = await facturacionService.getFacturasPorPeriodo(selectedAnio, selectedMes); setFacturas(data); } catch (err) { setFacturas([]); console.error(err); } finally { setLoading(false); } }, [selectedAnio, selectedMes, puedeGenerarFacturacion]); useEffect(() => { cargarFacturasDelPeriodo(); }, [cargarFacturasDelPeriodo]); const handleGenerarFacturacion = async () => { if (!window.confirm(`¿Está seguro de que desea generar la facturación para ${meses.find(m => m.value === selectedMes)?.label} de ${selectedAnio}? Este proceso creará registros de cobro para todas las suscripciones activas.`)) { return; } setLoading(true); setApiMessage(null); setApiError(null); try { const response = await facturacionService.generarFacturacionMensual(selectedAnio, selectedMes); setApiMessage(`${response.message}. Se generaron ${response.facturasGeneradas} facturas.`); await cargarFacturasDelPeriodo(); } catch (err: any) { const message = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Ocurrió un error al generar la facturación.'; setApiError(message); } finally { setLoading(false); } }; const handleGenerarArchivo = async () => { if (!window.confirm(`Se generará el archivo de débito para las facturas del período ${meses.find(m => m.value === selectedMes)?.label}/${selectedAnio} que estén en estado 'Pendiente de Cobro'. ¿Continuar?`)) { return; } setLoadingArchivo(true); setApiMessage(null); setApiError(null); try { const { fileContent, fileName } = await facturacionService.generarArchivoDebito(selectedAnio, selectedMes); const url = window.URL.createObjectURL(new Blob([fileContent])); const link = document.createElement('a'); link.href = url; link.setAttribute('download', fileName); document.body.appendChild(link); link.click(); link.parentNode?.removeChild(link); window.URL.revokeObjectURL(url); setApiMessage(`Archivo "${fileName}" generado y descargado exitosamente.`); cargarFacturasDelPeriodo(); } catch (err: any) { let message = 'Ocurrió un error al generar el archivo.'; if (axios.isAxiosError(err) && err.response) { const errorText = await err.response.data.text(); try { const errorJson = JSON.parse(errorText); message = errorJson.message || message; } catch { message = errorText || message; } } setApiError(message); } finally { setLoadingArchivo(false); } }; const handleMenuOpen = (event: React.MouseEvent, factura: FacturaDto) => { setAnchorEl(event.currentTarget); setSelectedFactura(factura); }; const handleMenuClose = () => { setAnchorEl(null); setSelectedFactura(null); }; const handleOpenPagoModal = () => { setPagoModalOpen(true); handleMenuClose(); }; const handleClosePagoModal = () => { setPagoModalOpen(false); }; const handleSubmitPagoModal = async (data: CreatePagoDto) => { setApiError(null); try { await facturacionService.registrarPagoManual(data); setApiMessage(`Pago para la factura #${data.idFactura} registrado exitosamente.`); cargarFacturasDelPeriodo(); } catch (err: any) { const message = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Error al registrar el pago.'; setApiError(message); throw err; } }; const handleSendEmail = async (idFactura: number) => { if (!window.confirm(`¿Está seguro de enviar la notificación de la factura #${idFactura} por email?`)) return; setApiMessage(null); setApiError(null); try { await facturacionService.enviarFacturaPorEmail(idFactura); setApiMessage(`El email para la factura #${idFactura} ha sido enviado a la cola de procesamiento.`); } catch (err: any) { setApiError(err.response?.data?.message || 'Error al intentar enviar el email.'); } finally { handleMenuClose(); } }; const handleFileChange = (event: React.ChangeEvent) => { if (event.target.files && event.target.files.length > 0) { setArchivoSeleccionado(event.target.files[0]); setApiMessage(null); setApiError(null); } }; const handleProcesarArchivo = async () => { if (!archivoSeleccionado) { setApiError("Por favor, seleccione un archivo de respuesta para procesar."); return; } setLoadingProceso(true); setApiMessage(null); setApiError(null); try { const response = await facturacionService.procesarArchivoRespuesta(archivoSeleccionado); setApiMessage(response.mensajeResumen); if (response.errores?.length > 0) { setApiError(`Se encontraron los siguientes problemas durante el proceso:\n${response.errores.join('\n')}`); } cargarFacturasDelPeriodo(); // Recargar para ver los estados finales } catch (err: any) { const message = axios.isAxiosError(err) && err.response?.data?.mensajeResumen ? err.response.data.mensajeResumen : 'Ocurrió un error crítico al procesar el archivo.'; setApiError(message); } finally { setLoadingProceso(false); setArchivoSeleccionado(null); } }; if (!puedeGenerarFacturacion) { return No tiene permiso para acceder a esta sección.; } return ( Facturación y Débito Automático 1. Generación de Facturación Este proceso calcula los importes a cobrar para todas las suscripciones activas en el período seleccionado. Mes Año 2. Generación de Archivo para Banco Crea el archivo de texto para enviar a "Pago Directo Galicia" con todas las facturas del período que estén listas para el cobro. 3. Procesar Respuesta del Banco Suba aquí el archivo de respuesta de Galicia para actualizar automáticamente el estado de las facturas a "Pagada" o "Rechazada". {archivoSeleccionado && {archivoSeleccionado.name}} {archivoSeleccionado && ( )} {apiError && {apiError}} {apiMessage && {apiMessage}} Facturas del Período IDSuscriptorPublicación ImporteEstadoNro. Factura Acciones {loading ? () : facturas.length === 0 ? (No hay facturas para el período seleccionado.) : (facturas.map(f => ( {f.idFactura} {f.nombreSuscriptor} {f.nombrePublicacion} ${f.importeFinal.toFixed(2)} {f.numeroFactura || '-'} handleMenuOpen(e, f)} disabled={f.estado === 'Pagada' || f.estado === 'Anulada'}> )))}
{selectedFactura && puedeRegistrarPago && ( Registrar Pago Manual )} {selectedFactura && puedeEnviarEmail && ( handleSendEmail(selectedFactura.idFactura)} disabled={!selectedFactura.numeroFactura} > Enviar Email )} setApiError(null)} />
); }; export default FacturacionPage;