feat: Implementación de Secciones, Recargos, Porc. Pago Dist. y backend E/S Dist.
Backend API:
- Recargos por Zona (`dist_RecargoZona`):
  - CRUD completo (Modelos, DTOs, Repositorio, Servicio, Controlador).
  - Endpoints anidados bajo `/publicaciones/{idPublicacion}/recargos`.
  - Lógica de negocio para vigencias (cierre/reapertura de períodos).
  - Auditoría en `dist_RecargoZona_H`.
- Porcentajes de Pago Distribuidores (`dist_PorcPago`):
  - CRUD completo (Modelos, DTOs, Repositorio, Servicio, Controlador).
  - Endpoints anidados bajo `/publicaciones/{idPublicacion}/porcentajespago`.
  - Lógica de negocio para vigencias.
  - Auditoría en `dist_PorcPago_H`.
- Porcentajes/Montos Pago Canillitas (`dist_PorcMonPagoCanilla`):
  - CRUD completo (Modelos, DTOs, Repositorio, Servicio, Controlador).
  - Endpoints anidados bajo `/publicaciones/{idPublicacion}/porcentajesmoncanilla`.
  - Lógica de negocio para vigencias.
  - Auditoría en `dist_PorcMonPagoCanilla_H`.
- Secciones de Publicación (`dist_dtPubliSecciones`):
  - CRUD completo (Modelos, DTOs, Repositorio, Servicio, Controlador).
  - Endpoints anidados bajo `/publicaciones/{idPublicacion}/secciones`.
  - Auditoría en `dist_dtPubliSecciones_H`.
- Entradas/Salidas Distribuidores (`dist_EntradasSalidas`):
  - Implementado backend (Modelos, DTOs, Repositorio, Servicio, Controlador).
  - Lógica para determinar precios/recargos/porcentajes aplicables.
  - Cálculo de monto y afectación de saldos de distribuidores en `cue_Saldos`.
  - Auditoría en `dist_EntradasSalidas_H`.
- Correcciones de Mapeo Dapper:
  - Aplicados alias explícitos en repositorios de RecargoZona, PorcPago, PorcMonCanilla, PubliSeccion,
    Canilla, Distribuidor y Precio para asegurar mapeo correcto de IDs y columnas.
Frontend React:
- Recargos por Zona:
  - `recargoZonaService.ts`.
  - `RecargoZonaFormModal.tsx` para crear/editar períodos de recargos.
  - `GestionarRecargosPublicacionPage.tsx` para listar y gestionar recargos por publicación.
- Porcentajes de Pago Distribuidores:
  - `porcPagoService.ts`.
  - `PorcPagoFormModal.tsx`.
  - `GestionarPorcentajesPagoPage.tsx`.
- Porcentajes/Montos Pago Canillitas:
  - `porcMonCanillaService.ts`.
  - `PorcMonCanillaFormModal.tsx`.
  - `GestionarPorcMonCanillaPage.tsx`.
- Secciones de Publicación:
  - `publiSeccionService.ts`.
  - `PubliSeccionFormModal.tsx`.
  - `GestionarSeccionesPublicacionPage.tsx`.
- Navegación:
  - Actualizadas rutas y menús para acceder a la gestión de recargos, porcentajes (dist. y canillita) y secciones desde la vista de una publicación.
- Layout:
  - Uso consistente de `Box` con Flexbox en lugar de `Grid` en nuevos modales y páginas para evitar errores de tipo.
			
			
This commit is contained in:
		
							
								
								
									
										189
									
								
								Frontend/src/pages/Distribucion/GestionarPorcMonCanillaPage.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										189
									
								
								Frontend/src/pages/Distribucion/GestionarPorcMonCanillaPage.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,189 @@ | ||||
| import React, { useState, useEffect, useCallback } from 'react'; | ||||
| import { useParams, useNavigate } from 'react-router-dom'; | ||||
| import { | ||||
|     Box, Typography, Button, Paper, IconButton, Menu, MenuItem, | ||||
|     Table, TableBody, TableCell, TableContainer, TableHead, TableRow, | ||||
|     CircularProgress, Alert, Chip | ||||
| } from '@mui/material'; | ||||
| import AddIcon from '@mui/icons-material/Add'; | ||||
| import MoreVertIcon from '@mui/icons-material/MoreVert'; | ||||
| import ArrowBackIcon from '@mui/icons-material/ArrowBack'; | ||||
| import EditIcon from '@mui/icons-material/Edit'; | ||||
| import DeleteIcon from '@mui/icons-material/Delete'; | ||||
|  | ||||
| import porcMonCanillaService from '../../services/Distribucion/porcMonCanillaService'; | ||||
| import publicacionService from '../../services/Distribucion/publicacionService'; | ||||
| import type { PorcMonCanillaDto } from '../../models/dtos/Distribucion/PorcMonCanillaDto'; | ||||
| import type { CreatePorcMonCanillaDto } from '../../models/dtos/Distribucion/CreatePorcMonCanillaDto'; | ||||
| import type { UpdatePorcMonCanillaDto } from '../../models/dtos/Distribucion/UpdatePorcMonCanillaDto'; | ||||
| import type { PublicacionDto } from '../../models/dtos/Distribucion/PublicacionDto'; | ||||
| import PorcMonCanillaFormModal from '../../components/Modals/Distribucion/PorcMonCanillaFormModal'; | ||||
| import { usePermissions } from '../../hooks/usePermissions'; | ||||
| import axios from 'axios'; | ||||
|  | ||||
| const GestionarPorcMonCanillaPage: React.FC = () => { | ||||
|   const { idPublicacion: idPublicacionStr } = useParams<{ idPublicacion: string }>(); | ||||
|   const navigate = useNavigate(); | ||||
|   const idPublicacion = Number(idPublicacionStr); | ||||
|  | ||||
|   const [publicacion, setPublicacion] = useState<PublicacionDto | null>(null); | ||||
|   const [items, setItems] = useState<PorcMonCanillaDto[]>([]); // Renombrado de 'porcentajes' a 'items' | ||||
|   const [loading, setLoading] = useState(true); | ||||
|   const [error, setError] = useState<string | null>(null); | ||||
|  | ||||
|   const [modalOpen, setModalOpen] = useState(false); | ||||
|   const [editingItem, setEditingItem] = useState<PorcMonCanillaDto | null>(null); | ||||
|   const [apiErrorMessage, setApiErrorMessage] = useState<string | null>(null); | ||||
|  | ||||
|   const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null); | ||||
|   const [selectedRow, setSelectedRow] = useState<PorcMonCanillaDto | null>(null); | ||||
|  | ||||
|   const { tienePermiso, isSuperAdmin } = usePermissions(); | ||||
|   // Permiso CG004 para porcentajes/montos de pago de canillitas | ||||
|   const puedeGestionar = isSuperAdmin || tienePermiso("CG004"); | ||||
|  | ||||
|   const cargarDatos = useCallback(async () => { | ||||
|     if (isNaN(idPublicacion)) { | ||||
|       setError("ID de Publicación inválido."); setLoading(false); return; | ||||
|     } | ||||
|     if (!puedeGestionar) { | ||||
|         setError("No tiene permiso para gestionar esta configuración."); setLoading(false); return; | ||||
|     } | ||||
|     setLoading(true); setError(null); setApiErrorMessage(null); | ||||
|     try { | ||||
|       const [pubData, data] = await Promise.all([ | ||||
|         publicacionService.getPublicacionById(idPublicacion), | ||||
|         porcMonCanillaService.getPorcMonCanillaPorPublicacion(idPublicacion) | ||||
|       ]); | ||||
|       setPublicacion(pubData); | ||||
|       setItems(data); | ||||
|     } catch (err: any) { | ||||
|       console.error(err); | ||||
|       if (axios.isAxiosError(err) && err.response?.status === 404) { | ||||
|         setError(`Publicación ID ${idPublicacion} no encontrada.`); | ||||
|       } else { | ||||
|         setError('Error al cargar los datos.'); | ||||
|       } | ||||
|     } finally { setLoading(false); } | ||||
|   }, [idPublicacion, puedeGestionar]); | ||||
|  | ||||
|   useEffect(() => { cargarDatos(); }, [cargarDatos]); | ||||
|  | ||||
|   const handleOpenModal = (item?: PorcMonCanillaDto) => { | ||||
|     setEditingItem(item || null); setApiErrorMessage(null); setModalOpen(true); | ||||
|   }; | ||||
|   const handleCloseModal = () => { | ||||
|     setModalOpen(false); setEditingItem(null); | ||||
|   }; | ||||
|  | ||||
|   const handleSubmitModal = async (data: CreatePorcMonCanillaDto | UpdatePorcMonCanillaDto, idPorcMon?: number) => { | ||||
|     setApiErrorMessage(null); | ||||
|     try { | ||||
|       if (editingItem && idPorcMon) { | ||||
|         await porcMonCanillaService.updatePorcMonCanilla(idPublicacion, idPorcMon, data as UpdatePorcMonCanillaDto); | ||||
|       } else { | ||||
|         await porcMonCanillaService.createPorcMonCanilla(idPublicacion, data as CreatePorcMonCanillaDto); | ||||
|       } | ||||
|       cargarDatos(); | ||||
|     } catch (err: any) { | ||||
|       const message = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Error al guardar.'; | ||||
|       setApiErrorMessage(message); throw err; | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   const handleDelete = async (idPorcMonDelRow: number) => { | ||||
|     if (window.confirm(`¿Seguro de eliminar este registro (ID: ${idPorcMonDelRow})?`)) { | ||||
|        setApiErrorMessage(null); | ||||
|        try { | ||||
|         await porcMonCanillaService.deletePorcMonCanilla(idPublicacion, idPorcMonDelRow); | ||||
|         cargarDatos(); | ||||
|       } catch (err: any) { | ||||
|          const message = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Error al eliminar.'; | ||||
|          setApiErrorMessage(message); | ||||
|       } | ||||
|     } | ||||
|     handleMenuClose(); | ||||
|   }; | ||||
|  | ||||
|   const handleMenuOpen = (event: React.MouseEvent<HTMLElement>, item: PorcMonCanillaDto) => { | ||||
|     setAnchorEl(event.currentTarget); setSelectedRow(item); | ||||
|   }; | ||||
|   const handleMenuClose = () => { | ||||
|     setAnchorEl(null); setSelectedRow(null); | ||||
|   }; | ||||
|  | ||||
|   const formatDate = (dateString?: string | null) => dateString ? new Date(dateString + 'T00:00:00').toLocaleDateString('es-AR') : '-'; | ||||
|  | ||||
|   if (loading) return <Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}><CircularProgress /></Box>; | ||||
|   if (error) return <Alert severity="error" sx={{ m: 2 }}>{error}</Alert>; | ||||
|   if (!puedeGestionar) return <Alert severity="error" sx={{ m: 2 }}>Acceso denegado.</Alert>; | ||||
|  | ||||
|   return ( | ||||
|     <Box sx={{ p: 2 }}> | ||||
|         <Button startIcon={<ArrowBackIcon />} onClick={() => navigate(`/distribucion/publicaciones`)} sx={{ mb: 2 }}> | ||||
|             Volver a Publicaciones | ||||
|         </Button> | ||||
|       <Typography variant="h4" gutterBottom>Porcentajes/Montos Pago Canillita: {publicacion?.nombre || 'Cargando...'}</Typography> | ||||
|       <Typography variant="subtitle1" color="text.secondary" gutterBottom>Empresa: {publicacion?.nombreEmpresa || '-'}</Typography> | ||||
|  | ||||
|       <Paper sx={{ p: 2, mb: 2 }}> | ||||
|          {puedeGestionar && ( | ||||
|             <Button variant="contained" startIcon={<AddIcon />} onClick={() => handleOpenModal()} sx={{ mb: 2 }}> | ||||
|                 Agregar Configuración | ||||
|             </Button> | ||||
|         )} | ||||
|       </Paper> | ||||
|  | ||||
|       {apiErrorMessage && <Alert severity="error" sx={{my: 2}}>{apiErrorMessage}</Alert>} | ||||
|  | ||||
|       <TableContainer component={Paper}> | ||||
|         <Table size="small"> | ||||
|           <TableHead><TableRow> | ||||
|               <TableCell sx={{fontWeight: 'bold'}}>Canillita</TableCell> | ||||
|               <TableCell sx={{fontWeight: 'bold'}}>Vig. Desde</TableCell> | ||||
|               <TableCell sx={{fontWeight: 'bold'}}>Vig. Hasta</TableCell> | ||||
|               <TableCell align="right" sx={{fontWeight: 'bold'}}>Valor</TableCell> | ||||
|               <TableCell align="center" sx={{fontWeight: 'bold'}}>Tipo</TableCell> | ||||
|               <TableCell align="center" sx={{fontWeight: 'bold'}}>Estado</TableCell> | ||||
|               <TableCell align="right" sx={{fontWeight: 'bold'}}>Acciones</TableCell> | ||||
|           </TableRow></TableHead> | ||||
|           <TableBody> | ||||
|             {items.length === 0 ? ( | ||||
|               <TableRow><TableCell colSpan={7} align="center">No hay configuraciones definidas.</TableCell></TableRow> | ||||
|             ) : ( | ||||
|               items.sort((a,b) => new Date(b.vigenciaD).getTime() - new Date(a.vigenciaD).getTime() || a.nomApeCanilla.localeCompare(b.nomApeCanilla)) | ||||
|               .map((item) => ( | ||||
|                   <TableRow key={item.idPorcMon} hover> | ||||
|                   <TableCell>{item.nomApeCanilla}</TableCell><TableCell>{formatDate(item.vigenciaD)}</TableCell> | ||||
|                   <TableCell>{formatDate(item.vigenciaH)}</TableCell> | ||||
|                   <TableCell align="right">{item.esPorcentaje ? `${item.porcMon.toFixed(2)}%` : `$${item.porcMon.toFixed(2)}`}</TableCell> | ||||
|                   <TableCell align="center">{item.esPorcentaje ? <Chip label="%" color="primary" size="small" variant="outlined"/> : <Chip label="Monto" color="secondary" size="small" variant="outlined"/>}</TableCell> | ||||
|                   <TableCell align="center">{!item.vigenciaH ? <Chip label="Activo" color="success" size="small" /> : <Chip label="Cerrado" size="small" />}</TableCell> | ||||
|                   <TableCell align="right"> | ||||
|                     <IconButton onClick={(e) => handleMenuOpen(e, item)} disabled={!puedeGestionar}><MoreVertIcon /></IconButton> | ||||
|                   </TableCell> | ||||
|                   </TableRow> | ||||
|               )))} | ||||
|           </TableBody> | ||||
|         </Table> | ||||
|       </TableContainer> | ||||
|  | ||||
|       <Menu anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleMenuClose}> | ||||
|         {puedeGestionar && selectedRow && ( | ||||
|             <MenuItem onClick={() => { handleOpenModal(selectedRow); handleMenuClose(); }}><EditIcon fontSize="small" sx={{mr:1}}/> Editar/Cerrar</MenuItem>)} | ||||
|         {puedeGestionar && selectedRow && ( | ||||
|             <MenuItem onClick={() => handleDelete(selectedRow.idPorcMon)}><DeleteIcon fontSize="small" sx={{mr:1}}/> Eliminar</MenuItem>)} | ||||
|       </Menu> | ||||
|  | ||||
|       {idPublicacion && | ||||
|         <PorcMonCanillaFormModal | ||||
|             open={modalOpen} onClose={handleCloseModal} onSubmit={handleSubmitModal} | ||||
|             idPublicacion={idPublicacion} initialData={editingItem} | ||||
|             errorMessage={apiErrorMessage} clearErrorMessage={() => setApiErrorMessage(null)} | ||||
|         /> | ||||
|       } | ||||
|     </Box> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| export default GestionarPorcMonCanillaPage; | ||||
		Reference in New Issue
	
	Block a user