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.
		
	
		
			
				
	
	
		
			130 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			130 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import React, { useState, useEffect } from 'react';
 | |
| import {
 | |
|     Modal, Box, Typography, TextField, Button, CircularProgress, Alert,
 | |
|     FormControlLabel, Checkbox
 | |
| } from '@mui/material';
 | |
| import type { PubliSeccionDto } from '../../../models/dtos/Distribucion/PubliSeccionDto';
 | |
| import type { CreatePubliSeccionDto } from '../../../models/dtos/Distribucion/CreatePubliSeccionDto';
 | |
| import type { UpdatePubliSeccionDto } from '../../../models/dtos/Distribucion/UpdatePubliSeccionDto';
 | |
| 
 | |
| const modalStyle = { /* ... (mismo estilo) ... */
 | |
|     position: 'absolute' as 'absolute',
 | |
|     top: '50%',
 | |
|     left: '50%',
 | |
|     transform: 'translate(-50%, -50%)',
 | |
|     width: { xs: '90%', sm: 450 },
 | |
|     bgcolor: 'background.paper',
 | |
|     border: '2px solid #000',
 | |
|     boxShadow: 24,
 | |
|     p: 4,
 | |
| };
 | |
| 
 | |
| interface PubliSeccionFormModalProps {
 | |
|   open: boolean;
 | |
|   onClose: () => void;
 | |
|   onSubmit: (data: CreatePubliSeccionDto | UpdatePubliSeccionDto, idSeccion?: number) => Promise<void>;
 | |
|   idPublicacion: number; // Siempre necesario para la creación
 | |
|   initialData?: PubliSeccionDto | null;
 | |
|   errorMessage?: string | null;
 | |
|   clearErrorMessage: () => void;
 | |
| }
 | |
| 
 | |
| const PubliSeccionFormModal: React.FC<PubliSeccionFormModalProps> = ({
 | |
|   open,
 | |
|   onClose,
 | |
|   onSubmit,
 | |
|   idPublicacion,
 | |
|   initialData,
 | |
|   errorMessage,
 | |
|   clearErrorMessage
 | |
| }) => {
 | |
|   const [nombre, setNombre] = useState('');
 | |
|   const [estado, setEstado] = useState(true); // Default a activa
 | |
| 
 | |
|   const [loading, setLoading] = useState(false);
 | |
|   const [localErrorNombre, setLocalErrorNombre] = useState<string | null>(null);
 | |
| 
 | |
|   const isEditing = Boolean(initialData);
 | |
| 
 | |
|   useEffect(() => {
 | |
|     if (open) {
 | |
|         setNombre(initialData?.nombre || '');
 | |
|         setEstado(initialData ? initialData.estado : true);
 | |
|         setLocalErrorNombre(null);
 | |
|         clearErrorMessage();
 | |
|     }
 | |
|   }, [open, initialData, clearErrorMessage]);
 | |
| 
 | |
|   const validate = (): boolean => {
 | |
|     let isValid = true;
 | |
|     if (!nombre.trim()) {
 | |
|         setLocalErrorNombre('El nombre de la sección es obligatorio.');
 | |
|         isValid = false;
 | |
|     }
 | |
|     return isValid;
 | |
|   };
 | |
| 
 | |
|   const handleInputChange = () => {
 | |
|     if (localErrorNombre) setLocalErrorNombre(null);
 | |
|     if (errorMessage) clearErrorMessage();
 | |
|   };
 | |
| 
 | |
|   const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
 | |
|     event.preventDefault();
 | |
|     clearErrorMessage();
 | |
|     if (!validate()) return;
 | |
| 
 | |
|     setLoading(true);
 | |
|     try {
 | |
|       if (isEditing && initialData) {
 | |
|         const dataToSubmit: UpdatePubliSeccionDto = { nombre, estado };
 | |
|         await onSubmit(dataToSubmit, initialData.idSeccion);
 | |
|       } else {
 | |
|         const dataToSubmit: CreatePubliSeccionDto = {
 | |
|             idPublicacion, // Viene de props
 | |
|             nombre,
 | |
|             estado
 | |
|         };
 | |
|         await onSubmit(dataToSubmit);
 | |
|       }
 | |
|       onClose();
 | |
|     } catch (error: any) {
 | |
|       console.error("Error en submit de PubliSeccionFormModal:", error);
 | |
|       // El error de API se maneja en la página
 | |
|     } finally {
 | |
|        setLoading(false);
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   return (
 | |
|     <Modal open={open} onClose={onClose}>
 | |
|       <Box sx={modalStyle}>
 | |
|         <Typography variant="h6" component="h2" gutterBottom>
 | |
|           {isEditing ? 'Editar Sección' : 'Agregar Nueva Sección'}
 | |
|         </Typography>
 | |
|         <Box component="form" onSubmit={handleSubmit} sx={{ mt: 1 }}>
 | |
|             <TextField label="Nombre de la Sección" value={nombre} required
 | |
|                 onChange={(e) => {setNombre(e.target.value); handleInputChange();}}
 | |
|                 margin="dense" fullWidth error={!!localErrorNombre} helperText={localErrorNombre || ''}
 | |
|                 disabled={loading} autoFocus
 | |
|             />
 | |
|             <FormControlLabel
 | |
|                 control={<Checkbox checked={estado} onChange={(e) => setEstado(e.target.checked)} disabled={loading}/>}
 | |
|                 label="Activa" sx={{mt:1}}
 | |
|             />
 | |
| 
 | |
|           {errorMessage && <Alert severity="error" sx={{ mt: 2, width: '100%' }}>{errorMessage}</Alert>}
 | |
| 
 | |
|           <Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
 | |
|             <Button onClick={onClose} color="secondary" disabled={loading}>Cancelar</Button>
 | |
|             <Button type="submit" variant="contained" disabled={loading}>
 | |
|               {loading ? <CircularProgress size={24} /> : (isEditing ? 'Guardar Cambios' : 'Agregar Sección')}
 | |
|             </Button>
 | |
|           </Box>
 | |
|         </Box>
 | |
|       </Box>
 | |
|     </Modal>
 | |
|   );
 | |
| };
 | |
| 
 | |
| export default PubliSeccionFormModal; |