Compare commits

..

162 Commits

Author SHA1 Message Date
c27dc2a0ba Fix deploy
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 6m15s
2025-09-10 12:30:07 -03:00
24b1c07342 Test Up
Some checks failed
Optimized Build and Deploy / remote-build-and-deploy (push) Failing after 11s
2025-09-10 11:29:25 -03:00
cb64bbc1f5 Actualizar README.md
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 3m36s
2025-08-14 13:15:23 +00:00
057310ca47 Fix(Suscripciones): Insert en db arreglado y muestra en UI tipo factura
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 3m25s
- Se arregla error al insertar en la db el registro de factura "Alta"
- Se arregla UI por falla en la visualización del tipo de factura en la tabla de gestión de facturas.
2025-08-13 15:53:34 -03:00
e95c851e5b Feat(suscripciones): Implementa facturación pro-rata para altas y excluye del débito automático
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 3m45s
Se introduce una refactorización mayor del ciclo de facturación para manejar correctamente las suscripciones que inician en un período ya cerrado. Esto soluciona el problema de cobrar un mes completo a un nuevo suscriptor, mejorando la transparencia y la experiencia del cliente.

###  Nuevas Características y Lógica de Negocio

- **Facturación Pro-rata Automática (Factura de Alta):**
    - Al crear una nueva suscripción cuya fecha de inicio corresponde a un período de facturación ya cerrado, el sistema ahora calcula automáticamente el costo proporcional por los días restantes de ese mes.
    - Se genera de forma inmediata una nueva factura de tipo "Alta" por este monto parcial, separándola del ciclo de facturación mensual regular.

- **Exclusión del Débito Automático para Facturas de Alta:**
    - Se implementa una regla de negocio clave: las facturas de tipo "Alta" son **excluidas** del proceso de generación del archivo de débito automático para el banco.
    - Esto fuerza a que el primer cobro (el proporcional) se gestione a través de un medio de pago manual (efectivo, transferencia, etc.), evitando cargos inesperados en la cuenta bancaria del cliente.
    - El débito automático comenzará a operar normalmente a partir del primer ciclo de facturación completo.

### 🔄 Cambios en el Backend

- **Base de Datos:**
    - Se ha añadido la columna `TipoFactura` (`varchar(20)`) a la tabla `susc_Facturas`.
    - Se ha implementado una `CHECK constraint` para permitir únicamente los valores 'Mensual' y 'Alta'.

- **Servicios:**
    - **`SuscripcionService`:** Ahora contiene la lógica para detectar una alta retroactiva, invocar al `FacturacionService` para el cálculo pro-rata y crear la "Factura de Alta" y su detalle correspondiente dentro de la misma transacción.
    - **`FacturacionService`:** Expone públicamente el método `CalcularImporteParaSuscripcion` y se ha actualizado `ObtenerResumenesDeCuentaPorPeriodo` para que envíe la propiedad `TipoFactura` al frontend.
    - **`DebitoAutomaticoService`:** El método `GetFacturasParaDebito` ahora filtra y excluye explícitamente las facturas donde `TipoFactura = 'Alta'`.

### 🎨 Mejoras en la Interfaz de Usuario (Frontend)

- **`ConsultaFacturasPage.tsx`:**
    - **Nueva Columna:** Se ha añadido una columna "Tipo Factura" en la tabla de detalle, que muestra un `Chip` distintivo para identificar fácilmente las facturas de "Alta".
    - **Nuevo Filtro:** Se ha agregado un nuevo menú desplegable para filtrar la vista por "Tipo de Factura" (`Todas`, `Mensual`, `Alta`), permitiendo a los administradores auditar rápidamente los nuevos ingresos.
2025-08-13 14:55:24 -03:00
038faefd35 Fix: Formato de Archivo de Débito Modificado
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 4m23s
2025-08-12 12:49:56 -03:00
da50c052f1 Fix: Configuración SMTP
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 6m46s
- No se toma la configuración SMTP del .env por falla de lecturas.
- Se incluyen las configuraciones en appsettings.json
2025-08-12 11:18:30 -03:00
5781713b13 Fix: Menú Reportes
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 7m12s
- Fix del menú de reportes que impedía el recorrido del mismo.
- Se quita la apertura predeterminada de una opción del menú de Reportes.
2025-08-12 10:33:36 -03:00
9f8d577265 Limpieza de Comantarios
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 7m59s
2025-08-11 15:44:16 -03:00
b594a48fde Feat: Se modifican visual de menú reportes
- Se limita la visual del menú de reportes a los usuarios según los permisos de acceso.
- Se soluciona bug en mensaje al ingresar usuario y/o clave inválidos.
2025-08-11 15:42:23 -03:00
2e7d1e36be Feat(suscripciones): Implementa manejo de pagos parciales en facturas
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 8m3s
Se introduce una refactorización completa del sistema de registro de pagos para manejar correctamente los abonos parciales, asegurando que el estado de la factura y el saldo pendiente se reflejen con precisión tanto en el backend como en la interfaz de usuario.

### 🐛 Problema Solucionado

- Anteriormente, el sistema no reconocía los pagos parciales. Una factura permanecía en estado "Pendiente" hasta que el monto total era cubierto, y la interfaz de usuario siempre mostraba el 100% del saldo como pendiente, lo cual era incorrecto y confuso.

###  Nuevas Características y Mejoras

- **Nuevo Estado de Factura "Pagada Parcialmente":**
    - Se introduce un nuevo estado para las facturas que han recibido uno o más pagos pero cuyo saldo aún no es cero.
    - El `PagoService` ahora actualiza el estado de la factura a "Pagada Parcialmente" cuando recibe un abono que no cubre el total.

- **Mejoras en la Interfaz de Usuario (`ConsultaFacturasPage`):**
    - **Nuevas Columnas:** Se han añadido las columnas "Pagado" y "Saldo" a la tabla de detalle de facturas, mostrando explícitamente el monto abonado y el restante.
    - **Visualización de Estado:** El `Chip` de estado ahora muestra "Pagada Parcialmente" con un color distintivo (azul/primary) para una rápida identificación visual.
    - **Cálculo de Saldo Correcto:** El saldo pendiente total por suscriptor y el saldo para el modal de pago manual ahora se calculan correctamente, restando el `totalPagado` del `importeFinal`.

### 🔄 Cambios en el Backend

- **`PagoService`:** Se actualizó la lógica para establecer el estado de la factura (`Pendiente`, `Pagada Parcialmente`, `Pagada`) basado en el `nuevoTotalPagado` después de registrar un pago.
- **`FacturacionService`:** El método `ObtenerResumenesDeCuentaPorPeriodo` ahora calcula correctamente el `SaldoPendienteTotal` y pasa la propiedad `TotalPagado` al DTO del frontend.
- **DTOs:** Se actualizó `FacturaConsolidadaDto` para incluir la propiedad `TotalPagado`.
2025-08-11 15:15:08 -03:00
dd2277fce2 Fix: Mail Host
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 9m57s
2025-08-11 14:35:02 -03:00
9412556fa8 Feat: Se añade seccion de permisos para Suscripciones
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 7m51s
- Se añade la lista de asignación de permisos de Suscripciones a la UI
- Se añade el permiso de acceso a los reportes de suscripciones
2025-08-11 12:36:51 -03:00
8c194b8441 Refactor: Externaliza configuración de MailSettings a archivo .env
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 6m31s
Para mejorar la seguridad y seguir las mejores prácticas, se ha externalizado la configuración sensible de `MailSettings` (credenciales SMTP) del archivo `appsettings.json` a un archivo `.env` no versionado.

### Cambios Realizados

- **Implementación de .env:**
    - Se ha creado un archivo `.env` en la raíz del proyecto para almacenar las variables de entorno relacionadas con el servicio de correo.
    - Se ha añadido el paquete NuGet `DotNetEnv` al proyecto para permitir la carga de este archivo.

- **Modificación del Arranque:**
    - Se ha modificado `Program.cs` para que cargue las variables del archivo `.env` al inicio de la aplicación, haciéndolas disponibles para el sistema de configuración de .NET.

- **Limpieza de `appsettings.json`:**
    - Se han eliminado los valores sensibles (usuario, contraseña, etc.) de la sección `MailSettings` en `appsettings.json`. El archivo ahora sirve como plantilla de la estructura de configuración sin exponer credenciales.
2025-08-11 11:29:14 -03:00
1a288fcfa5 Feat: Implementa auditoría de envíos masivos y mejora de procesos
Se introduce un sistema completo para auditar los envíos masivos de correos durante el cierre mensual y se refactoriza la interfaz de usuario de procesos para una mayor claridad y escalabilidad. Además, se mejora la lógica de negocio para la gestión de bajas de suscripciones.

###  Nuevas Características

- **Auditoría de Envíos Masivos (Cierre Mensual):**
    - Se crea una nueva tabla `com_LotesDeEnvio` para registrar cada ejecución del proceso de facturación mensual.
    - El `FacturacionService` ahora crea un "lote" al iniciar el cierre, registra el resultado de cada envío de email individual asociándolo a dicho lote, y actualiza las estadísticas finales (enviados, fallidos) al terminar.
    - Se implementa un nuevo `LotesEnvioController` con un endpoint para consultar los detalles de cualquier lote de envío histórico.

### 🔄 Refactorización y Mejoras

- **Rediseño de la Página de Procesos:**
    - La antigua página "Facturación" se renombra a `CierreYProcesosPage` y se rediseña completamente utilizando una interfaz de Pestañas (Tabs).
    - **Pestaña "Procesos Mensuales":** Aisla las acciones principales (Generar Cierre, Archivo de Débito, Procesar Respuesta), mostrando un resumen del resultado del último envío.
    - **Pestaña "Historial de Cierres":** Muestra una tabla con todos los lotes de envío pasados y permite al usuario ver los detalles de cada uno en un modal.

- **Filtros para el Historial de Cierres:**
    - Se añaden filtros por Mes y Año a la pestaña de "Historial de Cierres", permitiendo al usuario buscar y auditar procesos pasados de manera eficiente. El filtrado se realiza en el backend para un rendimiento óptimo.

- **Lógica de `FechaFin` Obligatoria para Bajas:**
    - Se implementa una regla de negocio crucial: al cambiar el estado de una suscripción a "Pausada" o "Cancelada", ahora es obligatorio establecer una `FechaFin`.
    - **Frontend:** El modal de suscripciones ahora gestiona esto automáticamente, haciendo el campo `FechaFin` requerido y visible según el estado seleccionado.
    - **Backend:** Se añade una validación en `SuscripcionService` como segunda capa de seguridad para garantizar la integridad de los datos.

### 🐛 Corrección de Errores

- **Reporte de Distribución:** Se corrigió un bug en la generación del PDF donde la columna de fecha no mostraba la "Fecha de Baja" para las suscripciones finalizadas. Ahora se muestra la fecha correcta según la sección (Altas o Bajas).
- **Errores de Compilación y Dependencias:** Se solucionaron varios errores de compilación en el backend, principalmente relacionados con la falta de registro de los nuevos repositorios (`ILoteDeEnvioRepository`, `IEmailLogService`, etc.) en el contenedor de inyección de dependencias (`Program.cs`).
- **Errores de Tipado en Frontend:** Se corrigieron múltiples errores de TypeScript en `CierreYProcesosPage` debidos a la inconsistencia entre `PascalCase` (C#) y `camelCase` (JSON/TypeScript), asegurando un mapeo correcto de los datos de la API.
2025-08-11 11:14:03 -03:00
7dc0940001 Feat: Implementa auditoría de envíos de email y mejora la UX
Se introduce un sistema completo de logging para todas las comunicaciones por correo electrónico y se realizan mejoras significativas en la experiencia del usuario, tanto en la retroalimentación del sistema como en la estética de los emails enviados al cliente.

###  Nuevas Características

- **Auditoría y Log de Envíos de Email:**
    - Se ha creado una nueva tabla `com_EmailLogs` en la base de datos para registrar cada intento de envío de correo.
    - El `EmailService` ahora centraliza toda la lógica de logging, registrando automáticamente la fecha, destinatario, asunto, estado (`Enviado` o `Fallido`), y mensajes de error detallados.
    - Se implementó un nuevo `EmailLogService` y `EmailLogRepository` para gestionar estos registros.

- **Historial de Envíos en la Interfaz de Usuario:**
    - Se añade un nuevo ícono de "Historial" (<span style="color: #607d8b;">&#x1F4E7;</span>) junto a cada factura en la página de "Consulta de Facturas".
    - Al hacer clic, se abre un modal que muestra una tabla detallada con todos los intentos de envío para esa factura, incluyendo el estado y el motivo del error (si lo hubo).
    - Esto proporciona una trazabilidad completa y una herramienta de diagnóstico para el usuario final.

### 🔄 Refactorización y Mejoras

- **Mensajes de Éxito Dinámicos:**
    - Se ha mejorado la retroalimentación al enviar una factura por PDF. El sistema ahora muestra un mensaje de éxito específico, como "El email... se ha enviado correctamente a suscriptor@email.com", en lugar de un mensaje técnico genérico.
    - Se ajustó la cadena de llamadas (`Controller` -> `Service`) para que el email del destinatario esté disponible para la respuesta de la API.

- **Diseño Unificado de Emails:**
    - Se ha rediseñado el template HTML para el "Aviso de Cuenta Mensual" para que coincida con la estética del email de "Envío de Factura PDF".
    - Ambos correos ahora presentan un diseño profesional y consistente, con cabecera, logo y pie de página, reforzando la imagen de marca.

- **Manejo de Errores de Email Mejorado:**
    - El `EmailService` ahora captura excepciones específicas de la librería `MailKit` (ej. `SmtpCommandException`).
    - Esto permite registrar en el log errores mucho más precisos y útiles, como rechazos de destinatarios por parte del servidor (`User unknown`), fallos de autenticación, etc., que ahora son visibles en el `Tooltip` del historial.
2025-08-09 21:12:11 -03:00
5a806eda38 Feat: Mejora UI de Cuenta Corriente y corrige colores en email de aviso
Este commit introduce significativas mejoras de usabilidad en la página de gestión de ajustes del suscriptor y corrige la representación visual de los ajustes en el email de notificación mensual.

###  Nuevas Características y Mejoras de UI

- **Nuevos Filtros en Cuenta Corriente del Suscriptor:**
    - Se han añadido nuevos filtros desplegables en la página de "Cuenta Corriente" para filtrar los ajustes por **Estado** (`Pendiente`, `Aplicado`, `Anulado`) y por **Tipo** (`Crédito`, `Débito`).
    - Esta mejora permite a los usuarios encontrar registros específicos de manera mucho más rápida y eficiente, especialmente para suscriptores con un largo historial de ajustes.

- **Visualización Mejorada del Estado de Ajuste:**
    - La columna "Estado" en la tabla de ajustes ahora muestra el número de factura oficial (ej. `A-0001-12345`) si un ajuste ha sido aplicado y la factura ya está numerada.
    - Si la factura aún no tiene un número oficial, se muestra una referencia al ID interno (ej. `ID Interno #64`) para mantener la trazabilidad.
    - Para soportar esto, se ha enriquecido el `AjusteDto` en el backend para incluir el `NumeroFacturaAplicado`.

### 🐛 Corrección y Refactorización

- **Corrección de Colores en Email de Aviso:**
    - Se han invertido los colores de los montos de ajuste en el email de aviso mensual enviado al cliente para alinearlos con la perspectiva del usuario.
    - **Créditos** (descuentos a favor del cliente) ahora se muestran en **verde** (positivo).
    - **Débitos** (cargos extra) ahora se muestran en **rojo** (negativo).
    - Este cambio mejora drásticamente la claridad del resumen de cuenta y evita posibles confusiones.

### ⚙️ Cambios Técnicos de Soporte

- Se ha añadido el método `GetByIdsAsync` al `IFacturaRepository` para optimizar la obtención de datos de múltiples facturas en una sola consulta, evitando el problema N+1.
- El `AjusteService` ha sido actualizado para utilizar este nuevo método y poblar eficientemente la información de la factura en el DTO de ajuste que se envía al frontend.
2025-08-09 18:16:56 -03:00
21c5c1d7d9 Feat: Implementa Reporte de Distribución de Suscripciones y Refactoriza Gestión de Ajustes
Se introduce una nueva funcionalidad de reporte crucial para la logística y se realiza una refactorización mayor del sistema de ajustes para garantizar la correcta imputación contable.

###  Nuevas Características

- **Nuevo Reporte de Distribución de Suscripciones (RR011):**
    - Se añade un nuevo reporte en PDF que genera un listado de todas las suscripciones activas en un rango de fechas.
    - El reporte está diseñado para el equipo de reparto, incluyendo datos clave como nombre del suscriptor, dirección, teléfono, días de entrega y observaciones.
    - Se implementa el endpoint, la lógica de servicio, la consulta a la base de datos y el template de QuestPDF en el backend.
    - Se crea la página correspondiente en el frontend (React) con su selector de fechas y se añade la ruta y el enlace en el menú de reportes.

### 🔄 Refactorización Mayor

- **Asociación de Ajustes a Empresas:**
    - Se refactoriza por completo la entidad `Ajuste` para incluir una referencia obligatoria a una `IdEmpresa`.
    - **Motivo:** Corregir un error crítico en la lógica de negocio donde los ajustes de un suscriptor se aplicaban a la primera factura generada, sin importar a qué empresa correspondía el ajuste.
    - Se provee un script de migración SQL para actualizar el esquema de la base de datos (`susc_Ajustes`).
    - Se actualizan todos los DTOs, repositorios y servicios (backend) para manejar la nueva relación.
    - Se modifica el `FacturacionService` para que ahora aplique los ajustes pendientes correspondientes a cada empresa dentro de su bucle de facturación.
    - Se actualiza el formulario de creación/edición de ajustes en el frontend (React) para incluir un selector de empresa obligatorio.

### ️ Optimizaciones de Rendimiento

- **Solución de N+1 Queries:**
    - Se optimiza el método `ObtenerTodos` en `SuscriptorService` para obtener todas las formas de pago en una única consulta en lugar de una por cada suscriptor.
    - Se optimiza el método `ObtenerAjustesPorSuscriptor` en `AjusteService` para obtener todos los nombres de usuarios y empresas en dos consultas masivas, en lugar de N consultas individuales.
    - Se añade el método `GetByIdsAsync` al `IUsuarioRepository` y su implementación para soportar esta optimización.

### 🐛 Corrección de Errores

- Se corrigen múltiples errores en el script de migración de base de datos (uso de `GO` dentro de transacciones, error de "columna inválida").
- Se soluciona un error de SQL (`INSERT` statement) en `AjusteRepository` que impedía la creación de nuevos ajustes.
- Se corrige un bug en el `AjusteService` que causaba que el nombre de la empresa apareciera como "N/A" en la UI debido a un mapeo incompleto en el método optimizado.
- Se corrige la lógica de generación de emails en `FacturacionService` para mostrar correctamente el nombre de la empresa en cada sección del resumen de cuenta.
2025-08-09 17:39:21 -03:00
899e0a173f Refactor: Mejora la lógica de facturación y la UI
Este commit introduce una refactorización significativa en el módulo de
suscripciones para alinear el sistema con reglas de negocio clave:
facturación consolidada por empresa, cobro a mes adelantado con
imputación de ajustes diferida, y una interfaz de usuario más clara.

Backend:
- **Facturación por Empresa:** Se modifica `FacturacionService` para
  agrupar las suscripciones por cliente y empresa, generando una
  factura consolidada para cada combinación. Esto asegura la correcta
  separación fiscal.
- **Imputación de Ajustes:** Se ajusta la lógica para que la facturación
  de un período (ej. Septiembre) aplique únicamente los ajustes
  pendientes cuya fecha corresponde al período anterior (Agosto).
- **Cierre Secuencial:** Se implementa una validación en
  `GenerarFacturacionMensual` que impide generar la facturación de un
  período si el anterior no ha sido cerrado, garantizando el orden
  cronológico.
- **Emails Consolidados:** El proceso de notificación automática al
  generar el cierre ahora envía un único email consolidado por
  suscriptor, detallando los cargos de todas sus facturas/empresas.
- **Envío de PDF Individual:** Se refactoriza el endpoint de envío manual
  para que opere sobre una `idFactura` individual y adjunte el PDF
  correspondiente si existe.
- **Repositorios Mejorados:** Se optimizan y añaden métodos en
  `FacturaRepository` y `AjusteRepository` para soportar los nuevos
  requisitos de filtrado y consulta de datos consolidados.

Frontend:
- **Separación de Vistas:** La página de "Facturación" se divide en dos:
  - `ProcesosPage`: Para acciones masivas (generar cierre, archivo de
    débito, procesar respuesta).
  - `ConsultaFacturasPage`: Una nueva página dedicada a buscar,
    filtrar y gestionar facturas individuales con una interfaz de doble
    acordeón (Suscriptor -> Empresa).
- **Filtros Avanzados:** La página `ConsultaFacturasPage` ahora incluye
  filtros por nombre de suscriptor, estado de pago y estado de
  facturación.
- **Filtros de Fecha por Defecto:** La página de "Cuenta Corriente"
  ahora filtra por el mes actual por defecto para mejorar el rendimiento
  y la usabilidad.
- **Validación de Fechas:** Se añade lógica en los filtros de fecha para
  impedir la selección de rangos inválidos.
- **Validación de Monto de Pago:** El modal de pago manual ahora impide
  registrar un monto superior al saldo pendiente de la factura.
2025-08-08 09:48:15 -03:00
9cfe9d012e Feat: Implementa ABM y anulación de ajustes manuales
Este commit introduce la funcionalidad completa para la gestión de
ajustes manuales (créditos/débitos) en la cuenta corriente de un
suscriptor, cerrando un requerimiento clave detectado en el análisis
del flujo de trabajo manual.

Backend:
- Se añade la tabla `susc_Ajustes` para registrar movimientos manuales.
- Se crean el Modelo, DTOs, Repositorio y Servicio (`AjusteService`)
  para el ABM completo de los ajustes.
- Se implementa la lógica para anular ajustes que se encuentren en estado
  "Pendiente", registrando el usuario y fecha de anulación para
  mantener la trazabilidad.
- Se integra la lógica de aplicación de ajustes pendientes en el
  `FacturacionService`, afectando el `ImporteFinal` de la factura
  generada.
- Se añaden los nuevos endpoints en `AjustesController` para crear,
  listar y anular ajustes.

Frontend:
- Se crea el componente `CuentaCorrienteSuscriptorTab` para mostrar
  el historial de ajustes de un cliente.
- Se desarrolla el modal `AjusteFormModal` que permite a los usuarios
  registrar nuevos créditos o débitos.
- Se integra una nueva pestaña "Cuenta Corriente / Ajustes" en la
  vista de gestión de un suscriptor.
- Se añade la funcionalidad de "Anular" en la tabla de ajustes,
  permitiendo a los usuarios corregir errores antes del ciclo de
  facturación.
2025-08-01 14:38:15 -03:00
9e248efc84 Fix: CreatePromocionesDto
- Se separa el dto para el update del create.
2025-08-01 13:43:07 -03:00
84187a66df Feat: Implementa flujo completo de facturación y promociones
Este commit introduce la funcionalidad completa para la facturación mensual,
la gestión de promociones y la comunicación con el cliente en el módulo
de suscripciones.

Backend:
- Se añade el servicio de Facturación que calcula automáticamente los importes
  mensuales basándose en las suscripciones activas, días de entrega y precios.
- Se implementa el servicio DebitoAutomaticoService, capaz de generar el
  archivo de texto plano para "Pago Directo Galicia" y de procesar el
  archivo de respuesta para la conciliación de pagos.
- Se desarrolla el ABM completo para Promociones (Servicio, Repositorio,
  Controlador y DTOs), permitiendo la creación de descuentos por porcentaje
  o monto fijo.
- Se implementa la lógica para asignar y desasignar promociones a suscripciones
  específicas.
- Se añade un servicio de envío de email (EmailService) integrado con MailKit
  y un endpoint para notificar facturas a los clientes.
- Se crea la lógica para registrar pagos manuales (efectivo, tarjeta, etc.)
  y actualizar el estado de las facturas.
- Se añaden todos los permisos necesarios a la base de datos para
  segmentar el acceso a las nuevas funcionalidades.

Frontend:
- Se crea la página de Facturación, que permite al usuario seleccionar un
  período, generar la facturación, listar los resultados y generar el archivo
  de débito para el banco.
- Se implementa la funcionalidad para subir y procesar el archivo de
  respuesta del banco, actualizando la UI en consecuencia.
- Se añade la página completa para el ABM de Promociones.
- Se integra un modal en la gestión de suscripciones para asignar y
  desasignar promociones a un cliente.
- Se añade la opción "Enviar Email" en el menú de acciones de las facturas,
  conectada al nuevo endpoint del backend.
- Se completan y corrigen los componentes `PagoManualModal` y `FacturacionPage`
  para incluir la lógica de registro de pagos y solucionar errores de TypeScript.
2025-08-01 12:53:17 -03:00
b14c5de1b4 Feat: Implementación de módulos ABM de suscripciones por cliente 2025-07-31 10:24:26 -03:00
d62ca7feb3 Feat: Tipos de datos y Servicios 2025-07-30 11:14:33 -03:00
f09c795fb0 Feat: Se agregan servicios y controladores para ABM de suscriptores 2025-07-30 09:48:05 -03:00
19e7192a16 Feat: Se añaden las capas de modelos y respositorios para el modulo de Suscripciones 2025-07-29 14:11:50 -03:00
7e4d3282fb feat(Reportes): Refactoriza vista Dist. General y corrige totales PDF
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 5m35s
Frontend:
- Se refactoriza la página `ReporteListadoDistribucionGeneralPage.tsx` para reemplazar la tabla HTML estándar por el componente `DataGrid` de MUI X.
- Se implementa el cálculo y la visualización de una fila de totales para las tablas de "Resumen Diario" y "Promedios por Día", mejorando la legibilidad y consistencia con otros reportes.
- Se actualiza la exportación a Excel para incluir estas nuevas filas de totales.
- Se corrigen errores de tipado (TypeScript) relacionados con la importación de DTOs.

Backend:
- Se ajusta la lógica en `ListadoDistribucionGeneralViewModel.cs` para calcular correctamente la fila "General" de promedios en la exportación a PDF.
- Anteriormente, el promedio se calculaba incorrectamente dividiendo por el total de días del mes. Ahora, se calcula un promedio real basado únicamente en los días con actividad (tirada > 0), asegurando que los datos del PDF coincidan con los de la interfaz.
2025-07-28 11:34:40 -03:00
28c1b88a92 Feat: Implementar modificación de Tiradas y mejorar UX/UI
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 12m54s
**Backend:**
- Se ha añadido el endpoint `PUT /api/tiradas` para manejar la modificación de una Tirada, identificada por su clave única (fecha, idPublicacion, idPlanta).
- Se implementó un mecanismo de actualización granular para las secciones de la tirada (`bob_RegPublicaciones`), reemplazando la estrategia anterior de "eliminar todo y recrear".
  - La nueva lógica reconcilia el estado entrante con el de la base de datos, realizando operaciones individuales de `INSERT`, `UPDATE` y `DELETE` para cada sección.
  - Esto mejora significativamente el rendimiento y proporciona un historial de auditoría mucho más preciso.
- Se añadieron los DTOs `UpdateTiradaRequestDto` y `UpdateDetalleSeccionTiradaDto` para soportar el nuevo payload de modificación.
- Se expandieron los repositorios `IRegPublicacionSeccionRepository` y `IPubliSeccionRepository` con métodos para operaciones granulares (`UpdateAsync`, `DeleteByIdAsync`, `GetByIdsAndPublicacionAsync`).

**Frontend:**
- El componente `TiradaFormModal` ha sido refactorizado para funcionar tanto en modo "Crear" como en modo "Editar", recibiendo una prop `tiradaToEdit`.
- Se implementó una lógica de carga asíncrona robusta que obtiene los datos completos de una tirada antes de abrir el modal en modo edición.

**Mejoras de UI/UX:**
- Se ha rediseñado el layout de la lista de tiradas en `GestionarTiradasPage`:
  - Los botones de acción (Editar, Borrar) y los datos clave (chips de ejemplares y páginas) ahora se encuentran en una cabecera estática.
  - Estos elementos permanecen fijos en la parte superior y no se desplazan al expandir el acordeón, mejorando la consistencia visual.
- Se ha mejorado la tabla de secciones dentro del `TiradaFormModal`:
  - El botón "+ AGREGAR SECCIÓN" ahora está fijo en la parte inferior de la tabla, permaneciendo siempre visible incluso cuando la lista de secciones tiene scroll.
  - Al agregar una nueva sección, la lista se desplaza automáticamente hacia abajo para mostrar la nueva fila.
2025-07-23 14:05:58 -03:00
052141a45b Actualizar README.md
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 2m57s
2025-07-22 12:55:22 +00:00
9e8ccf6cfb Actualizar README.md
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 2m59s
2025-07-22 12:47:38 +00:00
9c225845c2 Añadir README.md
Some checks failed
Optimized Build and Deploy / remote-build-and-deploy (push) Has been cancelled
2025-07-22 12:47:15 +00:00
f46dd82e27 Fix: Dropdown Focus
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 6m4s
2025-07-21 13:00:11 -03:00
c251a0adf4 Feat: Solicitudes Contaduría
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 6m57s
- Siglas Días en Reporte Dist. General
- Mostrar Publicaciones Deshabilitadas en Selectores de Reportes
2025-07-21 12:44:21 -03:00
3e1ac6f742 Fix: Cambios solicitados. Parte 1
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 6m18s
2025-07-18 21:46:07 -03:00
a35a3a66ea Fix: Filtro Fecha Tiradas
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 5m10s
2025-07-18 17:04:12 -03:00
c96d259892 Implementación AnomalIA - Fix de dropdowns y permisos.
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 5m17s
2025-06-30 15:26:14 -03:00
95aa09d62a Fix Limpieza de Images y Mensaje de Auditoria NPM
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 4m2s
2025-06-27 18:59:03 -03:00
dc52c9aff2 Auditoria de NPM y Fix de Paquetes
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 5m2s
2025-06-27 18:24:30 -03:00
d60ec7ffd7 Reversión de deploy
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 3m8s
2025-06-25 23:00:56 -03:00
03cda5cdbe Reversión total de JWT Key
Some checks failed
Optimized Build and Deploy / remote-build-and-deploy (push) Failing after 1m6s
2025-06-25 22:57:24 -03:00
c6aec21e70 Try escape de variables remotas con \
Some checks failed
Optimized Build and Deploy / remote-build-and-deploy (push) Failing after 27s
2025-06-25 22:51:39 -03:00
a1a185c7b6 Edición de Compose y Se quita Https Redirection
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 13m58s
2025-06-25 22:25:58 -03:00
6921effab0 Test Para .Env con Echo
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 8m3s
2025-06-25 22:00:07 -03:00
cea51ee93c Reversión con Indentación y <<- para Cat
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 2m43s
2025-06-25 21:46:36 -03:00
ea225d650e Fix indentación Cat 2025-06-25 21:45:14 -03:00
ff02cef2ca Try de JWT Key en .Env con Secret de Gitea
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 9m24s
2025-06-25 21:23:06 -03:00
3c522a33af Fix Producción IP Nueva
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 3m1s
2025-06-25 20:53:16 -03:00
b553affe7f Cambio de ip de produccion para la API
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 3m21s
2025-06-25 19:43:36 -03:00
ab232c481d Test https redirection
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 6m23s
2025-06-25 19:22:26 -03:00
6a1d935a61 Reversión, se implementa solo tolerancia de Kuma.
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 6m22s
2025-06-24 22:21:11 -03:00
68376291da Retry 2216
Some checks failed
Optimized Build and Deploy with Session Authentication / remote-build-and-deploy (push) Failing after 11s
2025-06-24 22:16:40 -03:00
26856f26bf CAmbio en Secrets (User - Pass)
Some checks failed
Optimized Build and Deploy with Session Authentication / remote-build-and-deploy (push) Failing after 8s
2025-06-24 22:06:09 -03:00
230332568f Fix 2154
Some checks failed
Optimized Build and Deploy with Maintenance Window (Robust) / remote-build-and-deploy (push) Failing after 9s
2025-06-24 21:54:08 -03:00
99d3db68d9 Retry 2151
Some checks failed
Optimized Build and Deploy with Maintenance Window / remote-build-and-deploy (push) Failing after 8s
2025-06-24 21:51:27 -03:00
b5a68d1825 Test con PUT a Kuma
Some checks failed
Optimized Build and Deploy with Maintenance Window / remote-build-and-deploy (push) Failing after 10s
2025-06-24 21:49:15 -03:00
de54e5e200 Test Kuma Api con Endpoints
Some checks failed
Optimized Build and Deploy with Maintenance Window / remote-build-and-deploy (push) Failing after 10s
2025-06-24 21:39:32 -03:00
673fd55037 Fix Deploy 2137
Some checks failed
Optimized Build and Deploy with Maintenance Window / remote-build-and-deploy (push) Failing after 11s
2025-06-24 21:37:45 -03:00
edb8a5e56c Intento de Manejo de "Mantenimiento" de Kuma desde el deploy.
Some checks failed
Optimized Build and Deploy with Maintenance Window / remote-build-and-deploy (push) Failing after 48s
2025-06-24 21:33:49 -03:00
fd11ef9005 Se añade servicio de HealtCheck de SqlServer
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 7m19s
Se pretende utilizar mediante Kuma
2025-06-24 20:51:57 -03:00
229eb937f5 QuestPdf Implementado en la totalidad de reportes.
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 7m55s
2025-06-24 12:52:37 -03:00
a5bcbefa52 Try con QuestPDF
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 7m36s
Se elimina Puppeteer y Chromium. Se utiliza QuestPDF para mayor velocidad y sin Razor.
2025-06-20 19:04:23 -03:00
1373bcf9ca Retry Usando Docker Build Nativo
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 11m48s
2025-06-20 12:02:10 -03:00
3a0f0a4bf8 Fix. Se usa un repositorio de caché ficticio local que no requiere autenticación.
Some checks failed
Optimized Build and Deploy / remote-build-and-deploy (push) Failing after 17s
2025-06-20 11:58:39 -03:00
16f991e04b Fix para Build local
Some checks failed
Optimized Build and Deploy / remote-build-and-deploy (push) Failing after 17s
2025-06-20 11:54:09 -03:00
26ffc7c0b2 Fix para omitir sudo
Some checks failed
Optimized Build and Deploy / remote-build-and-deploy (push) Failing after 38s
2025-06-20 11:48:24 -03:00
35f62c3322 Fix ruta del deploy
Some checks failed
Optimized Build and Deploy / remote-build-and-deploy (push) Failing after 12s
2025-06-20 11:46:01 -03:00
a80a6f964f Test de optimización de Pipeline con Cache.
Some checks failed
Optimized Build and Deploy / remote-build-and-deploy (push) Failing after 11s
2025-06-20 11:38:26 -03:00
b4b4f8b322 Fix libs
All checks were successful
Build and Deploy / remote-build-and-deploy (push) Successful in 52m31s
2025-06-20 08:21:45 -03:00
60a27621c1 Retry 2149
All checks were successful
Build and Deploy / remote-build-and-deploy (push) Successful in 29m58s
2025-06-19 21:49:24 -03:00
221c41dc7d Intento de lectura de los cshtml en la ruta correcta.
All checks were successful
Build and Deploy / remote-build-and-deploy (push) Successful in 24m2s
2025-06-19 20:17:53 -03:00
8561c84d71 Ahora debería de copiar los cshtml, antes los compilaba junto con los demas archivos. Para este caso se dejan planos.
All checks were successful
Build and Deploy / remote-build-and-deploy (push) Successful in 23m27s
2025-06-19 19:48:28 -03:00
d30a0033f7 Fix para include de appsettings.json
All checks were successful
Build and Deploy / remote-build-and-deploy (push) Successful in 23m24s
2025-06-19 18:50:24 -03:00
bf275c1cf2 Fix property
All checks were successful
Build and Deploy / remote-build-and-deploy (push) Successful in 21m28s
2025-06-19 18:20:33 -03:00
716707cd9f Cambio Ruta RDLC A CSHTML
Some checks failed
Build and Deploy / remote-build-and-deploy (push) Failing after 7m48s
2025-06-19 17:48:16 -03:00
f97e8ead15 Fix 1717
All checks were successful
Build and Deploy / remote-build-and-deploy (push) Successful in 24m50s
2025-06-19 17:17:50 -03:00
703b8766b1 Fix
All checks were successful
Build and Deploy / remote-build-and-deploy (push) Successful in 24m18s
2025-06-19 16:10:38 -03:00
4b94377827 Fix copia de cshtml templates.
All checks were successful
Build and Deploy / remote-build-and-deploy (push) Successful in 25m12s
2025-06-19 15:35:38 -03:00
975a1e6d26 Test Reportes con Razor y Puppeteer
All checks were successful
Build and Deploy / remote-build-and-deploy (push) Successful in 28m23s
2025-06-19 14:47:43 -03:00
8591945eb4 Se añaden librerias necesarias faltantes.
All checks were successful
Build and Deploy / remote-build-and-deploy (push) Successful in 19m20s
2025-06-19 12:06:29 -03:00
5da5a0edea Test cambio de usuario en archivo Docker del backend
All checks were successful
Build and Deploy / remote-build-and-deploy (push) Successful in 18m10s
2025-06-19 11:37:40 -03:00
88c0762962 Retry formato ISO para fechas.
All checks were successful
Build and Deploy / remote-build-and-deploy (push) Successful in 18m22s
2025-06-19 10:53:50 -03:00
f4601b7520 Reversión.
All checks were successful
Build and Deploy / remote-build-and-deploy (push) Successful in 17m7s
Se añade GDIPlus para reportes.
2025-06-19 10:21:47 -03:00
dc1be0a073 Test formato fecha para rdlc.
All checks were successful
Build and Deploy / remote-build-and-deploy (push) Successful in 12m51s
2025-06-19 09:41:52 -03:00
b957a3ef5b Test date format Reports.
All checks were successful
Build and Deploy / remote-build-and-deploy (push) Successful in 23m25s
2025-06-18 16:03:02 -03:00
5df3f661e0 Test Culture-Invariant Date Format
All checks were successful
Build and Deploy / remote-build-and-deploy (push) Successful in 20m17s
2025-06-18 15:32:22 -03:00
b4b596efd9 Key en json directa reversión.
All checks were successful
Build and Deploy / remote-build-and-deploy (push) Successful in 16m42s
2025-06-18 14:50:03 -03:00
c07a4aa0f4 Verificador añadido y docker-compose.yml modificado.
All checks were successful
Build and Deploy / remote-build-and-deploy (push) Successful in 11m51s
2025-06-18 14:12:57 -03:00
f0f3c2b771 Correción de Sintaxis.
All checks were successful
Build and Deploy / remote-build-and-deploy (push) Successful in 17m13s
2025-06-18 13:48:45 -03:00
373449ba48 Vuelta a Key en Secret.
All checks were successful
Build and Deploy / remote-build-and-deploy (push) Successful in 15m54s
2025-06-18 13:24:46 -03:00
6d6ec22258 Retry .env 1259
All checks were successful
Build and Deploy / remote-build-and-deploy (push) Successful in 17m20s
2025-06-18 12:59:55 -03:00
19ea4b23a0 Ajustes copia RDLC y variables de entorno.
All checks were successful
Build and Deploy / remote-build-and-deploy (push) Successful in 21m46s
2025-06-18 12:15:41 -03:00
d514864986 Key directa en json 2
All checks were successful
Build and Deploy / remote-build-and-deploy (push) Successful in 11m40s
2025-06-17 23:00:47 -03:00
29ba94e425 Testeo de Key directa en json.
Some checks failed
Build and Deploy / remote-build-and-deploy (push) Failing after 15s
2025-06-17 22:58:20 -03:00
c7d75ce76d Retry .env 2239
All checks were successful
Build and Deploy / remote-build-and-deploy (push) Successful in 12m3s
2025-06-17 22:39:52 -03:00
6a2729e25b Retry .env 2234
Some checks failed
Build and Deploy / build-and-deploy (push) Failing after 27s
2025-06-17 22:34:15 -03:00
41282b61cd Retry 2208
All checks were successful
Build and Deploy / remote-build-and-deploy (push) Successful in 19m15s
2025-06-17 22:08:28 -03:00
bf91f9dc7c Ya me estoy volviendo loco, copio y pego sugerencias sin mirarlas.
All checks were successful
Build and Deploy / remote-build-and-deploy (push) Successful in 19m16s
2025-06-17 21:44:34 -03:00
673fde163d Retry .env 4
Some checks failed
Build and Deploy / build-and-push (push) Successful in 1m28s
Build and Deploy / deploy (push) Failing after 1m18s
2025-06-17 21:39:45 -03:00
88c9f22108 Vuelta
Some checks failed
Build and Deploy / remote-build-and-deploy (push) Has been cancelled
2025-06-17 21:37:07 -03:00
2f5a159650 Retry .env 3
Some checks failed
Build and Deploy / remote-build-and-deploy (push) Failing after 14s
2025-06-17 21:36:22 -03:00
08a7c27e08 Retry .env 2
Some checks failed
Build and Deploy / remote-build-and-deploy (push) Failing after 15s
2025-06-17 21:33:18 -03:00
4448f02df0 Retry .env
Some checks failed
Build and Deploy / remote-build-and-deploy (push) Failing after 14s
2025-06-17 21:30:06 -03:00
7e84c3f74c Sintaxis
Some checks failed
Build and Deploy / remote-build-and-deploy (push) Failing after 26s
2025-06-17 21:21:44 -03:00
662da26ce8 Sintaxis EOT
Some checks failed
Build and Deploy / remote-build-and-deploy (push) Failing after 13m41s
2025-06-17 21:04:55 -03:00
0ef4e2d8f0 Indentación
All checks were successful
Build and Deploy / remote-build-and-deploy (push) Successful in 12m30s
2025-06-17 20:47:55 -03:00
55ec8b5612 Secrets mediante .env 2025-06-17 20:46:12 -03:00
bdcf8c60b7 Correción de sintaxis para el JWT Token.
All checks were successful
Build and Deploy / remote-build-and-deploy (push) Successful in 13m34s
2025-06-17 20:22:32 -03:00
481fce8115 Fix pasaje de JWT Token por Secret.
All checks were successful
Build and Deploy / remote-build-and-deploy (push) Successful in 17m27s
2025-06-17 19:53:55 -03:00
1d9e23cc66 Correción de Rutas.
All checks were successful
Build and Deploy / remote-build-and-deploy (push) Successful in 13m17s
2025-06-17 18:29:32 -03:00
5d2c577c5e Se añade nginx.conf personalizado y se expone swagger.
All checks were successful
Build and Deploy / remote-build-and-deploy (push) Successful in 24m18s
2025-06-17 15:08:38 -03:00
9e268e7fd4 Listo para Producción!
All checks were successful
Build and Deploy / remote-build-and-deploy (push) Successful in 16m22s
2025-06-17 14:36:05 -03:00
ee9e125b17 Fix path DTOs!
All checks were successful
Build and Deploy / remote-build-and-deploy (push) Successful in 11m4s
2025-06-17 13:50:35 -03:00
722c6ee4b2 Final CI/CD?
Some checks failed
Build and Deploy / remote-build-and-deploy (push) Failing after 10m31s
2025-06-17 13:35:40 -03:00
0ea909db23 Retry 1313
Some checks failed
Build and Deploy / remote-build-and-deploy (push) Failing after 14m12s
2025-06-17 13:13:40 -03:00
5af737fdfe Retry 1311
Some checks failed
Build and Deploy / remote-build-and-deploy (push) Failing after 18s
2025-06-17 13:11:20 -03:00
cd7b51b29a Retry 1306
Some checks failed
Build and Deploy / remote-build-and-deploy (push) Failing after 20s
2025-06-17 13:06:10 -03:00
adef45c466 Retry 1303
Some checks failed
Build and Deploy / build-and-deploy (push) Failing after 16s
2025-06-17 13:03:57 -03:00
6bafe0541d Retry 1301
Some checks failed
Build and Deploy / remote-build-and-deploy (push) Failing after 17s
2025-06-17 13:01:25 -03:00
a544fd7438 Retry 1259
All checks were successful
Build and Deploy / remote-build-and-deploy (push) Successful in 15s
2025-06-17 12:59:12 -03:00
20d04b947b Retry 1256
Some checks failed
Build and Deploy / build-and-deploy (push) Failing after 20s
2025-06-17 12:56:18 -03:00
34e98b283f Retry 1246
Some checks failed
Build and Deploy / build-and-deploy (push) Failing after 32s
2025-06-17 12:46:32 -03:00
a7c8944e78 no message 2025-06-17 12:45:55 -03:00
e84a6fb531 Retry 1229
Some checks failed
Build and Deploy / build-and-deploy (push) Failing after 21s
2025-06-17 12:33:55 -03:00
e39c8a9491 Retry 1225 2025-06-17 12:33:55 -03:00
8da73278db Retry 1222 2025-06-17 12:33:55 -03:00
ea2ee0c2fa Retry 1217 2025-06-17 12:33:55 -03:00
4c5d920021 Retry 1156 2025-06-17 12:33:55 -03:00
84332c947c Retry 1151 2025-06-17 12:33:55 -03:00
7b73bb3acf Retry 1149 2025-06-17 12:33:55 -03:00
c36a742c00 Retry 1141 2025-06-17 12:33:55 -03:00
27edd88f63 Retry 1138 2025-06-17 12:33:55 -03:00
3c8a66c09b Retry 1133 2025-06-17 12:33:55 -03:00
906f73640c Retry 1131 2025-06-17 12:33:55 -03:00
5bb3d064e4 Retry 1128 2025-06-17 12:33:55 -03:00
55765f9846 Retry 1115 2025-06-17 12:33:54 -03:00
66c091eb79 Retry 1111 2025-06-17 12:33:54 -03:00
47b8b47265 Retry 1106 2025-06-17 12:33:54 -03:00
47bc88a248 Retry 1101 2025-06-17 12:33:54 -03:00
6941de5276 Retry 1051 2025-06-17 12:33:54 -03:00
f841252783 Retry 1040 2025-06-17 12:33:54 -03:00
2febe9967b Retry 1035 2025-06-17 12:33:54 -03:00
3b21890797 Retry 1033 2025-06-17 12:33:54 -03:00
ccf1647a3d Retry 1027 2025-06-17 12:33:54 -03:00
3ff5197637 Cambios 1026 2025-06-17 12:33:54 -03:00
0bc455450a Retry 1016 2025-06-17 12:33:54 -03:00
56eb65dbdf Retry 1014 2025-06-17 12:33:54 -03:00
ca729bd875 Fix deploy 2025-06-17 12:33:54 -03:00
b4a219795a 1001 2025-06-17 12:33:54 -03:00
4b95de9ba2 Fix Kaniko (+1 squashed commits)
Squashed commits:

[98c2593] Retry Kaniko
2025-06-17 12:33:53 -03:00
8f488405ee Instalar Docker en el Entorno del Job
Some checks failed
Build and Deploy / build-and-deploy (push) Failing after 47s
2025-06-17 09:54:56 -03:00
373766a415 Cambios en secrets.
Some checks failed
Build and Deploy / build-and-deploy (push) Failing after 16s
2025-06-17 09:52:17 -03:00
1526579b66 Test logs
Some checks failed
Build and Deploy / build-and-deploy (push) Failing after 12s
2025-06-17 09:44:53 -03:00
72f55b72d8 Vuelta a Runners de Gitea.
Some checks failed
Build and Deploy / build-and-deploy (push) Failing after 14s
2025-06-16 20:08:53 -03:00
21f4facb31 Ajustes de CI/CD.
fix: Run jobs on runner host to resolve network issues

Reseteo de estado por fallas del deply.

Reversión.

Nuevo testeo de CI/CD.

Reversión 2.

Fix

Reversión 3

Test con sh-runner

Reversión antes de cambio de foco.

Sin sh-runner.

No puede resolver gitea. Debe usar el secreto REGISTRY_URL.

Test con Gitea 1.21.11

Reversion 1.24.0

Va

Este

Final?

A ver...

Nuevo

Cambio de Enfoque para CI/CD. Se intenta uso de Drone.

Fix de yml para Drone.

Retry .drone.yml

Fix problema de indentación.

Retry yml

Va

Retry mil

Nuevo test

Fix: Configure docker plugin for insecure registry

Forzar la network.

Retry 0142

Test Webhook

Se agregan debug-network para verificar valores.

Se agregan daemon_dns para poder resolver los dominios de nuget desde los contenedores de despliegue.

Cambio de enfoque. Parametro para MTU (Maximum Transmission Unit) añadidos.

Prueba: Usar el Socket de Docker del Host

Fix indentación. Y Trusted en Drone.

Linter: duplicate step name

Todos los registros de contenedores (Docker Hub, Gitea, etc.) exigen que los nombres de las imágenes de Docker estén en minúsculas. El pipeline está intentando crear una imagen llamada dmolinari/GestionIntegralWeb-backend, pero la parte GestionIntegralWeb contiene mayúsculas.
Esto ocurre porque la variable de Drone ${DRONE_REPO_NAME} toma el nombre directamente de Gitea, que en este caso es GestionIntegralWeb. La sintaxis es: ${VARIABLE,,}.

Se añade la URL del registro al nombre del repo

Cabio de Variable por valor directo.

Retry 1806

Retry 1807

Webhook gitea Enable.

Added: privileged: true

Test con Kaniko

Fix yml

Retest

Cambio de imagen kaniko

Cambio por host
2025-06-16 19:16:24 -03:00
0b8096e734 Removido el .gitignore individual del Frontend. 2025-06-13 14:05:33 -03:00
4e7ba4e5b2 Limpieza completa del índice de Git para aplicar .gitignore 2025-06-13 14:02:45 -03:00
97b0efa3dd Se añade .gitignore global. 2025-06-13 13:48:31 -03:00
cec471b4b1 Fix Selectores de Fechas Reporte Existencia de Papel.
Se agrega Total a Liquidar para la E/S de Canillitas.
2025-06-13 13:23:05 -03:00
b04a3b99bf 1. Funcionalidad Principal: Auditoría General
Se creó una nueva sección de "Auditoría" en la aplicación, diseñada para ser accedida por SuperAdmins.
Se implementó una página AuditoriaGeneralPage.tsx que actúa como un visor centralizado para el historial de cambios de múltiples entidades del sistema.
2. Backend:
Nuevo Controlador (AuditoriaController.cs): Centraliza los endpoints para obtener datos de las tablas de historial (_H).
Servicios y Repositorios Extendidos:
Se añadieron métodos GetHistorialAsync y ObtenerHistorialAsync a las capas de repositorio y servicio para cada una de las siguientes entidades, permitiendo consultar sus tablas _H con filtros:
Usuarios (gral_Usuarios_H)
Pagos de Distribuidores (cue_PagosDistribuidor_H)
Notas de Crédito/Débito (cue_CreditosDebitos_H)
Entradas/Salidas de Distribuidores (dist_EntradasSalidas_H)
Entradas/Salidas de Canillitas (dist_EntradasSalidasCanillas_H)
Novedades de Canillitas (dist_dtNovedadesCanillas_H)
Tipos de Pago (cue_dtTipopago_H)
Canillitas (Maestro) (dist_dtCanillas_H)
Distribuidores (Maestro) (dist_dtDistribuidores_H)
Empresas (Maestro) (dist_dtEmpresas_H)
Zonas (Maestro) (dist_dtZonas_H)
Otros Destinos (Maestro) (dist_dtOtrosDestinos_H)
Publicaciones (Maestro) (dist_dtPublicaciones_H)
Secciones de Publicación (dist_dtPubliSecciones_H)
Precios de Publicación (dist_Precios_H)
Recargos por Zona (dist_RecargoZona_H)
Porcentajes Pago Distribuidores (dist_PorcPago_H)
Porcentajes/Montos Canillita (dist_PorcMonPagoCanilla_H)
Control de Devoluciones (dist_dtCtrlDevoluciones_H)
Tipos de Bobina (bob_dtBobinas_H)
Estados de Bobina (bob_dtEstadosBobinas_H)
Plantas de Impresión (bob_dtPlantas_H)
Stock de Bobinas (bob_StockBobinas_H)
Tiradas (Registro Principal) (bob_RegTiradas_H)
Secciones de Tirada (bob_RegPublicaciones_H)
Cambios de Parada de Canillitas (dist_CambiosParadasCanillas_H)
Ajustes Manuales de Saldo (cue_SaldoAjustesHistorial)
DTOs de Historial: Se crearon DTOs específicos para cada tabla de historial (ej. UsuarioHistorialDto, PagoDistribuidorHistorialDto, etc.) para transferir los datos al frontend, incluyendo el nombre del usuario que realizó la modificación.
Corrección de Lógica de Saldos: Se revisó y corrigió la lógica de afectación de saldos en los servicios PagoDistribuidorService y NotaCreditoDebitoService para asegurar que los débitos y créditos se apliquen correctamente.
3. Frontend:
Nuevo Servicio (auditoriaService.ts): Contiene métodos para llamar a cada uno de los nuevos endpoints de auditoría del backend.
Nueva Página (AuditoriaGeneralPage.tsx):
Permite al SuperAdmin seleccionar el "Tipo de Entidad" a auditar desde un dropdown.
Ofrece filtros comunes (rango de fechas, usuario modificador, tipo de acción) y filtros específicos que aparecen dinámicamente según la entidad seleccionada.
Utiliza un DataGrid de Material-UI para mostrar el historial, con columnas que se adaptan dinámicamente al tipo de entidad consultada.
Nuevos DTOs en TypeScript: Se crearon las interfaces correspondientes a los DTOs de historial del backend.
Gestión de Permisos:
La sección de Auditoría en MainLayout.tsx y su ruta en AppRoutes.tsx están protegidas para ser visibles y accesibles solo por SuperAdmins.
Se añadió un permiso de ejemplo AU_GENERAL_VIEW para ser usado si se decide extender el acceso en el futuro.
Corrección de Errores Menores: Se solucionó el problema del "parpadeo" del selector de fecha en GestionarNovedadesCanillaPage al adoptar un patrón de carga de datos más controlado, similar a otras páginas funcionales.
2025-06-12 19:36:21 -03:00
437b1e8864 Backend:
Diseño de un AuditoriaController con un patrón para añadir endpoints de historial para diferentes entidades.
Implementación de la lógica de servicio y repositorio para obtener datos de las tablas _H para:
Usuarios (gral_Usuarios_H)
Pagos de Distribuidores (cue_PagosDistribuidor_H)
Notas de Crédito/Débito (cue_CreditosDebitos_H)
Entradas/Salidas de Distribuidores (dist_EntradasSalidas_H)
Entradas/Salidas de Canillitas (dist_EntradasSalidasCanillas_H)
Novedades de Canillitas (dist_dtNovedadesCanillas_H)
Ajustes Manuales de Saldo (cue_SaldoAjustesHistorial)
Tipos de Pago (cue_dtTipopago_H)
Canillitas (Maestro) (dist_dtCanillas_H)
Distribuidores (Maestro) (dist_dtDistribuidores_H)
Empresas (Maestro) (dist_dtEmpresas_H)
DTOs específicos para cada tipo de historial, incluyendo NombreUsuarioModifico.
Frontend:
Servicio auditoriaService.ts con métodos para llamar a cada endpoint de historial.
Página AuditoriaGeneralPage.tsx con:
Selector de "Tipo de Entidad a Auditar".
Filtros comunes (Fechas, Usuario Modificador, Tipo de Modificación, ID Entidad).
Un DataGrid que muestra las columnas dinámicamente según el tipo de entidad seleccionada.
Lógica para cargar los datos correspondientes.
DTOs de historial en TypeScript.
Actualizaciones en AppRoutes.tsx y MainLayout.tsx para la nueva sección de Auditoría (restringida a SuperAdmin).
2025-06-09 19:37:07 -03:00
35e24ab7d2 Refinamiento de permisos y ajustes en controles. Añade gestión sobre saldos y visualización. Entre otros.. 2025-06-06 18:33:09 -03:00
8fb94f8cef Comenzando la implementación final de permisos y depuración. Se sigue... 2025-06-03 18:42:56 -03:00
641 changed files with 32447 additions and 55773 deletions

View File

@@ -0,0 +1,71 @@
name: Optimized Build and Deploy
on:
push:
branches:
- main
jobs:
remote-build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Run Optimized CI/CD Process on Host via SSH
run: |
set -e
# Configura SSH (sin cambios)
apt-get update -qq && apt-get install -y openssh-client git
mkdir -p ~/.ssh
echo "${{ secrets.PROD_SERVER_SSH_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H ${{ secrets.PROD_SERVER_HOST }} >> ~/.ssh/known_hosts
# Ejecuta en el host remoto
ssh ${{ secrets.PROD_SERVER_USER }}@${{ secrets.PROD_SERVER_HOST }} << 'EOSSH'
set -e
echo "--- INICIO DEL DESPLIEGUE OPTIMIZADO ---"
# 1. Preparar entorno
TEMP_DIR=$(mktemp -d)
REPO_OWNER="dmolinari"
REPO_NAME="gestionintegralweb"
GITEA_REPO_PATH="/var/lib/docker/volumes/gitea-stack_gitea-data/_data/git/repositories/${REPO_OWNER}/${REPO_NAME}.git"
echo "Clonando repositorio desde: $GITEA_REPO_PATH ..."
git clone "$GITEA_REPO_PATH" "$TEMP_DIR"
cd "$TEMP_DIR"
git checkout "${{ gitea.sha }}"
# 2. Construcción paralela
build_image() {
local dockerfile=$1
local image_name=$2
local context=$3
echo "Construyendo $image_name..."
docker build \
-t "$image_name" \
-f "$dockerfile" \
"$context"
}
echo "Construyendo imágenes en paralelo..."
(build_image "Backend/GestionIntegral.Api/Dockerfile" "dmolinari/gestionintegralweb-backend:latest" ".") &
(build_image "Frontend/Dockerfile" "dmolinari/gestionintegralweb-frontend:latest" ".") &
wait
# 3. Despliegue con Docker Compose
cd /opt/gestion-integral
export DB_SA_PASSWORD='${{ secrets.DB_SA_PASSWORD_SECRET }}'
echo "Recreando servicios de la aplicación..."
docker compose up -d --force-recreate
# 4. Limpieza
echo "Realizando limpieza..."
rm -rf "$TEMP_DIR"
docker image prune -f --filter "dangling=true"
echo "--- DESPLIEGUE COMPLETADO CON ÉXITO ---"
EOSSH

163
.gitignore vendored Normal file
View File

@@ -0,0 +1,163 @@
# ===================================================================
# Archivo .gitignore para un repositorio con Backend ASP.NET y Frontend React/Vite
# ===================================================================
# Dependencias y paquetes locales
# -------------------------------
node_modules/
dist/
dist-ssr/
*.local
# Archivos de log
# -------------------------------
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# Variables de entorno
# -------------------------------
.env.local
.env.development.local
.env.test.local
.env.production.local
# Archivos de caché y reportes de herramientas
# ---------------------------------------------
.eslintcache
.stylelintcache
.npm/
.pnpm-store/
coverage/
.nyc_output/
# ===================================================================
# SECCIÓN PARA PROYECTOS ASP.NET CORE (Backend)
# ===================================================================
# Archivos y carpetas generados por Visual Studio y Rider
# -------------------------------------------------------
*.sln.DotSettings.user
*.suo
*.user
*.userosscache
*.sln.docstates
*.vspscc
*.vssscc
.vs/
project.lock.json
project.fragment.lock.json
vs.lock.json
*.csproj.user
*.vcxproj.filters
*.pubxml
*.pubxml.user
# Carpetas de compilación y publicación
# --------------------------------------
[Bb]in/
[Oo]bj/
[Rr]elease/
[Rr]eleases/
x64/
x86/
build/
bld/
[Dd]ebug/
[Dd]ebug[Pp]ublic/
[Rr]elease[Pp]ublic/
[Tt]est[Rr]esult*/
[Bb]uild[Ss]uccess.txt
[Dd]ebug/
[Rr]elease/
**/[Bb]in
**/[Oo]bj
**/[Dd]ebug
**/[Rr]elease
**/[Ll]ogs
**/[Pp]ackages
*.nupkg
*.snupkg
# Archivos de configuración locales de usuario y secrets
# ------------------------------------------------------
# (appsettings.Development.json a veces se sube, pero si contiene secretos, no debería)
appsettings.Development.json
secrets.json
user-secrets.json
*.secrets.json
# Caché y logs de Resharper y Rider
# ----------------------------------
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
.idea/
# Archivos de Test
# -----------------
TestResults/
*.trx
# Archivos de publicación de base de datos
# -----------------------------------------
*.dacpac
*.dbschema
*.dbmdl
# Otros
# -----
*.log
*.tlog
*.psess
*.vsp
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tmp
*.tmp_proj
*.vsix
*.xaml.cs
# ===================================================================
# SECCIÓN PARA PROYECTOS REACT/VITE (Frontend)
# ===================================================================
# Archivos de caché y logs específicos de Vite/React
# ---------------------------------------------------
.DS_Store
.cache/
.history/
.idea/
.vscode/
# Editor-specific files
# ---------------------
*.swp
*~
*.orig
# Reportes
# --------
junit.xml
# Evitar subir imágenes/assets que deberían estar en el CDN en producción, si aplica.
# Si tus assets están en el repo, ignora esto.
# public/assets/production/
# ===================================================================
# Fin del archivo .gitignore
# ===================================================================

View File

@@ -0,0 +1,73 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using System.Collections.Generic;
using System.Threading.Tasks;
using GestionIntegral.Api.Dtos.Anomalia;
using GestionIntegral.Api.Services.Anomalia;
namespace GestionIntegral.Api.Controllers.Anomalia
{
[Route("api/alertas")]
[ApiController]
[Authorize]
public class AlertasController : ControllerBase
{
private readonly IAlertaService _alertaService;
public AlertasController(IAlertaService alertaService)
{
_alertaService = alertaService;
}
// GET: api/alertas
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<AlertaGenericaDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetAlertasNoLeidas()
{
var alertas = await _alertaService.ObtenerAlertasNoLeidasAsync();
return Ok(alertas);
}
// POST: api/alertas/{idAlerta}/marcar-leida
[HttpPost("{idAlerta:int}/marcar-leida")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> MarcarComoLeida(int idAlerta)
{
var (exito, error) = await _alertaService.MarcarComoLeidaAsync(idAlerta);
if (!exito)
{
return NotFound(new { message = error });
}
return NoContent();
}
// POST: api/alertas/marcar-grupo-leido
[HttpPost("marcar-grupo-leido")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> MarcarGrupoLeido([FromBody] MarcarGrupoLeidoRequestDto request)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var (exito, error) = await _alertaService.MarcarGrupoComoLeidoAsync(request.TipoAlerta, request.IdEntidad);
if (!exito)
{
return BadRequest(new { message = error });
}
return NoContent();
}
}
// DTO para el cuerpo del request de marcar grupo
public class MarcarGrupoLeidoRequestDto
{
[System.ComponentModel.DataAnnotations.Required]
public string TipoAlerta { get; set; } = string.Empty;
[System.ComponentModel.DataAnnotations.Required]
public int IdEntidad { get; set; }
}
}

View File

@@ -0,0 +1,715 @@
using GestionIntegral.Api.Services.Usuarios;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Security.Claims; // Para ClaimTypes
using GestionIntegral.Api.Services.Contables; // Para IPagoDistribuidorService, etc.
using GestionIntegral.Api.Services.Distribucion;
using GestionIntegral.Api.Dtos.Auditoria;
using GestionIntegral.Api.Dtos.Zonas;
using GestionIntegral.Api.Dtos.Distribucion;
using GestionIntegral.Api.Services.Impresion;
using GestionIntegral.Api.Dtos.Impresion;
using GestionIntegral.Api.Dtos.Usuarios;
namespace GestionIntegral.Api.Controllers
{
[Route("api/auditoria")]
[ApiController]
[Authorize]
public class AuditoriaController : ControllerBase
{
private readonly IUsuarioService _usuarioService;
private readonly IPagoDistribuidorService _pagoDistribuidorService;
private readonly INotaCreditoDebitoService _notaCreditoDebitoService;
private readonly IEntradaSalidaDistService _esDistService;
private readonly IDistribuidorService _distribuidorService;
private readonly IEntradaSalidaCanillaService _esCanillaService;
private readonly INovedadCanillaService _novedadCanillaService;
private readonly ICanillaService _canillaService;
private readonly ISaldoService _saldoService;
private readonly ITipoPagoService _tipoPagoService;
private readonly IEmpresaService _empresaService;
private readonly IZonaService _zonaService;
private readonly IOtroDestinoService _otroDestinoService;
private readonly IPublicacionService _publicacionService;
private readonly IPubliSeccionService _publiSeccionService;
private readonly IPrecioService _precioService;
private readonly IRecargoZonaService _recargoZonaService;
private readonly IPorcPagoService _porcPagoService;
private readonly IPorcMonCanillaService _porcMonCanillaService;
private readonly IControlDevolucionesService _controlDevolucionesService;
private readonly ITipoBobinaService _tipoBobinaService;
private readonly IEstadoBobinaService _estadoBobinaService;
private readonly IPlantaService _plantaService;
private readonly IStockBobinaService _stockBobinaService;
private readonly ITiradaService _tiradaService;
private readonly IPerfilService _perfilService;
private readonly IPermisoService _permisoService;
private readonly ICambioParadaService _cambioParadaService;
private readonly ILogger<AuditoriaController> _logger;
// Permiso general para ver cualquier auditoría.
// Podrías tener permisos más granulares por tipo de auditoría si es necesario.
private const string PermisoVerAuditoria = "AU_GENERAL_VIEW"; // Define este permiso
public AuditoriaController(
IUsuarioService usuarioService,
IPagoDistribuidorService pagoDistribuidorService,
INotaCreditoDebitoService notaCreditoDebitoService,
IEntradaSalidaDistService esDistService,
IDistribuidorService distribuidorService,
IEntradaSalidaCanillaService esCanillaService,
INovedadCanillaService novedadCanillaService,
ICanillaService canillaService,
ISaldoService saldoService,
ITipoPagoService tipoPagoService,
IEmpresaService empresaService,
IZonaService zonaService,
IOtroDestinoService otroDestinoService,
IPublicacionService publicacionService,
IPubliSeccionService publiSeccionService,
IPrecioService precioService,
IRecargoZonaService recargoZonaService,
IPorcPagoService porcPagoService,
IPorcMonCanillaService porcMonCanillaService,
IControlDevolucionesService controlDevolucionesService,
ITipoBobinaService tipoBobinaService,
IEstadoBobinaService estadoBobinaService,
IPlantaService plantaService,
IStockBobinaService stockBobinaService,
ITiradaService tiradaService,
IPerfilService perfilService,
IPermisoService permisoService,
ICambioParadaService cambioParadaService,
ILogger<AuditoriaController> logger)
{
_usuarioService = usuarioService;
_pagoDistribuidorService = pagoDistribuidorService;
_notaCreditoDebitoService = notaCreditoDebitoService;
_esDistService = esDistService;
_distribuidorService = distribuidorService;
_esCanillaService = esCanillaService;
_novedadCanillaService = novedadCanillaService;
_canillaService = canillaService;
_saldoService = saldoService;
_tipoPagoService = tipoPagoService;
_empresaService = empresaService;
_zonaService = zonaService;
_otroDestinoService = otroDestinoService;
_publicacionService = publicacionService;
_publiSeccionService = publiSeccionService;
_precioService = precioService;
_recargoZonaService = recargoZonaService;
_porcPagoService = porcPagoService;
_porcMonCanillaService = porcMonCanillaService;
_controlDevolucionesService = controlDevolucionesService;
_tipoBobinaService = tipoBobinaService;
_estadoBobinaService = estadoBobinaService;
_plantaService = plantaService;
_stockBobinaService = stockBobinaService;
_tiradaService = tiradaService;
_perfilService = perfilService;
_cambioParadaService = cambioParadaService;
_permisoService = permisoService;
_logger = logger;
}
private bool TienePermiso(string codAcc) => User.IsInRole("SuperAdmin") || User.HasClaim(c => c.Type == "permission" && c.Value == codAcc);
private int? GetCurrentUserId() // Podría no ser necesario aquí si solo filtramos por usuario modificador
{
if (int.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub"), out int userId)) return userId;
return null;
}
[HttpGet("pagos-distribuidores")]
[ProducesResponseType(typeof(IEnumerable<PagoDistribuidorHistorialDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetHistorialPagosDistribuidor(
[FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta,
[FromQuery] int? idUsuarioModifico, [FromQuery] string? tipoModificacion,
[FromQuery] int? idPagoAfectado) // Parámetro para filtrar por un ID de pago específico
{
if (!TienePermiso(PermisoVerAuditoria)) return Forbid(); // O un permiso más específico si lo creas
try
{
var historial = await _pagoDistribuidorService.ObtenerHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idPagoAfectado);
return Ok(historial ?? Enumerable.Empty<PagoDistribuidorHistorialDto>());
}
catch (Exception ex)
{
_logger.LogError(ex, "Error obteniendo historial de pagos de distribuidores.");
return StatusCode(500, "Error interno al obtener historial de pagos.");
}
}
[HttpGet("notas-credito-debito")]
[ProducesResponseType(typeof(IEnumerable<NotaCreditoDebitoHistorialDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetHistorialNotasCD(
[FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta,
[FromQuery] int? idUsuarioModifico, [FromQuery] string? tipoModificacion,
[FromQuery] int? idNotaAfectada) // ID de la nota original
{
if (!TienePermiso(PermisoVerAuditoria)) return Forbid();
try
{
var historial = await _notaCreditoDebitoService.ObtenerHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idNotaAfectada);
return Ok(historial ?? Enumerable.Empty<NotaCreditoDebitoHistorialDto>());
}
catch (Exception ex)
{
_logger.LogError(ex, "Error obteniendo historial de notas C/D.");
return StatusCode(500, "Error interno al obtener historial de notas C/D.");
}
}
[HttpGet("entradas-salidas-dist")]
[ProducesResponseType(typeof(IEnumerable<EntradaSalidaDistHistorialDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetHistorialEntradasSalidasDist(
[FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta,
[FromQuery] int? idUsuarioModifico, [FromQuery] string? tipoModificacion,
[FromQuery] int? idParteAfectada) // ID del movimiento original
{
if (!TienePermiso(PermisoVerAuditoria)) return Forbid();
try
{
var historial = await _esDistService.ObtenerHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idParteAfectada);
return Ok(historial ?? Enumerable.Empty<EntradaSalidaDistHistorialDto>());
}
catch (Exception ex)
{
_logger.LogError(ex, "Error obteniendo historial de E/S Distribuidores.");
return StatusCode(500, "Error interno al obtener historial de E/S Distribuidores.");
}
}
[HttpGet("entradas-salidas-canilla")]
[ProducesResponseType(typeof(IEnumerable<EntradaSalidaCanillaHistorialDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetHistorialEntradasSalidasCanilla(
[FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta,
[FromQuery] int? idUsuarioModifico, [FromQuery] string? tipoModificacion,
[FromQuery] int? idParteAfectada)
{
if (!TienePermiso(PermisoVerAuditoria)) return Forbid();
try
{
var historial = await _esCanillaService.ObtenerHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idParteAfectada);
return Ok(historial ?? Enumerable.Empty<EntradaSalidaCanillaHistorialDto>());
}
catch (Exception ex)
{
_logger.LogError(ex, "Error obteniendo historial de E/S Canillitas.");
return StatusCode(500, "Error interno al obtener historial de E/S Canillitas.");
}
}
[HttpGet("novedades-canilla")] // Endpoint consistente con el servicio frontend
[ProducesResponseType(typeof(IEnumerable<NovedadCanillaHistorialDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetHistorialNovedadesCanilla(
[FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta,
[FromQuery] int? idUsuarioModifico, [FromQuery] string? tipoModificacion,
[FromQuery] int? idNovedadAfectada) // ID de la novedad original
{
if (!TienePermiso(PermisoVerAuditoria)) return Forbid();
try
{
var historial = await _novedadCanillaService.ObtenerHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idNovedadAfectada);
return Ok(historial ?? Enumerable.Empty<NovedadCanillaHistorialDto>());
}
catch (Exception ex)
{
_logger.LogError(ex, "Error obteniendo historial de Novedades de Canillitas.");
return StatusCode(500, "Error interno al obtener historial de Novedades de Canillitas.");
}
}
[HttpGet("ajustes-saldo")]
[ProducesResponseType(typeof(IEnumerable<SaldoAjusteHistorialDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetHistorialAjustesSaldo(
[FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta,
[FromQuery] int? idUsuarioModifico,
[FromQuery] string? destino, [FromQuery] int? idDestino, [FromQuery] int? idEmpresa)
{
if (!TienePermiso(PermisoVerAuditoria)) return Forbid(); // O un permiso más específico si lo creas
try
{
var historial = await _saldoService.ObtenerHistorialAjustesAsync(fechaDesde, fechaHasta, idUsuarioModifico, destino, idDestino, idEmpresa);
return Ok(historial ?? Enumerable.Empty<SaldoAjusteHistorialDto>());
}
catch (Exception ex)
{
_logger.LogError(ex, "Error obteniendo historial de ajustes de saldo.");
return StatusCode(500, "Error interno al obtener historial de ajustes de saldo.");
}
}
[HttpGet("tipos-pago")]
[ProducesResponseType(typeof(IEnumerable<TipoPagoHistorialDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetHistorialTiposPago(
[FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta,
[FromQuery] int? idUsuarioModifico, [FromQuery] string? tipoModificacion,
[FromQuery] int? idTipoPagoAfectado)
{
if (!TienePermiso(PermisoVerAuditoria)) return Forbid();
try
{
// Asumiendo que _tipoPagoService está inyectado
var historial = await _tipoPagoService.ObtenerHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idTipoPagoAfectado);
return Ok(historial ?? Enumerable.Empty<TipoPagoHistorialDto>());
}
catch (Exception ex)
{
_logger.LogError(ex, "Error obteniendo historial de Tipos de Pago.");
return StatusCode(500, "Error interno al obtener historial de Tipos de Pago.");
}
}
[HttpGet("canillitas-maestro")] // Endpoint para el historial del maestro de canillitas
[ProducesResponseType(typeof(IEnumerable<CanillaHistorialDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetHistorialCanillitasMaestro(
[FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta,
[FromQuery] int? idUsuarioModifico, [FromQuery] string? tipoModificacion,
[FromQuery] int? idCanillaAfectado) // ID del canillita original
{
if (!TienePermiso(PermisoVerAuditoria)) return Forbid();
try
{
var historial = await _canillaService.ObtenerHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idCanillaAfectado);
return Ok(historial ?? Enumerable.Empty<CanillaHistorialDto>());
}
catch (Exception ex)
{
_logger.LogError(ex, "Error obteniendo historial de Canillitas (Maestro).");
return StatusCode(500, "Error interno al obtener historial de Canillitas (Maestro).");
}
}
[HttpGet("distribuidores-maestro")]
[ProducesResponseType(typeof(IEnumerable<DistribuidorHistorialDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetHistorialDistribuidoresMaestro(
[FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta,
[FromQuery] int? idUsuarioModifico, [FromQuery] string? tipoModificacion,
[FromQuery] int? idDistribuidorAfectado)
{
if (!TienePermiso(PermisoVerAuditoria)) return Forbid();
try
{
var historial = await _distribuidorService.ObtenerHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idDistribuidorAfectado);
return Ok(historial ?? Enumerable.Empty<DistribuidorHistorialDto>());
}
catch (Exception ex)
{
_logger.LogError(ex, "Error obteniendo historial de Distribuidores (Maestro).");
return StatusCode(500, "Error interno al obtener historial de Distribuidores (Maestro).");
}
}
[HttpGet("empresas-maestro")]
[ProducesResponseType(typeof(IEnumerable<EmpresaHistorialDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetHistorialEmpresasMaestro(
[FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta,
[FromQuery] int? idUsuarioModifico, [FromQuery] string? tipoModificacion,
[FromQuery] int? idEmpresaAfectada)
{
if (!TienePermiso(PermisoVerAuditoria)) return Forbid();
try
{
// Asumiendo que _empresaService está inyectado
var historial = await _empresaService.ObtenerHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idEmpresaAfectada);
return Ok(historial ?? Enumerable.Empty<EmpresaHistorialDto>());
}
catch (Exception ex)
{
_logger.LogError(ex, "Error obteniendo historial de Empresas (Maestro).");
return StatusCode(500, "Error interno al obtener historial de Empresas (Maestro).");
}
}
[HttpGet("zonas-maestro")]
[ProducesResponseType(typeof(IEnumerable<ZonaHistorialDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetHistorialZonasMaestro(
[FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta,
[FromQuery] int? idUsuarioModifico, [FromQuery] string? tipoModificacion,
[FromQuery] int? idZonaAfectada)
{
if (!TienePermiso(PermisoVerAuditoria)) return Forbid();
try
{
// Asumiendo que _zonaService está inyectado
var historial = await _zonaService.ObtenerHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idZonaAfectada);
return Ok(historial ?? Enumerable.Empty<ZonaHistorialDto>());
}
catch (Exception ex)
{
_logger.LogError(ex, "Error obteniendo historial de Zonas (Maestro).");
return StatusCode(500, "Error interno al obtener historial de Zonas (Maestro).");
}
}
[HttpGet("otros-destinos-maestro")]
[ProducesResponseType(typeof(IEnumerable<OtroDestinoHistorialDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetHistorialOtrosDestinosMaestro(
[FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta,
[FromQuery] int? idUsuarioModifico, [FromQuery] string? tipoModificacion,
[FromQuery] int? idOtroDestinoAfectado)
{
if (!TienePermiso(PermisoVerAuditoria)) return Forbid();
try
{
var historial = await _otroDestinoService.ObtenerHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idOtroDestinoAfectado);
return Ok(historial ?? Enumerable.Empty<OtroDestinoHistorialDto>());
}
catch (Exception ex)
{
_logger.LogError(ex, "Error obteniendo historial de Otros Destinos (Maestro).");
return StatusCode(500, "Error interno al obtener historial de Otros Destinos (Maestro).");
}
}
[HttpGet("publicaciones-maestro")]
[ProducesResponseType(typeof(IEnumerable<PublicacionHistorialDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetHistorialPublicacionesMaestro(
[FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta,
[FromQuery] int? idUsuarioModifico, [FromQuery] string? tipoModificacion,
[FromQuery] int? idPublicacionAfectada)
{
if (!TienePermiso(PermisoVerAuditoria)) return Forbid();
try
{
var historial = await _publicacionService.ObtenerHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idPublicacionAfectada);
return Ok(historial ?? Enumerable.Empty<PublicacionHistorialDto>());
}
catch (Exception ex)
{
_logger.LogError(ex, "Error obteniendo historial de Publicaciones (Maestro).");
return StatusCode(500, "Error interno al obtener historial de Publicaciones (Maestro).");
}
}
[HttpGet("publi-secciones-maestro")]
[ProducesResponseType(typeof(IEnumerable<PubliSeccionHistorialDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetHistorialPubliSeccionesMaestro(
[FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta,
[FromQuery] int? idUsuarioModifico, [FromQuery] string? tipoModificacion,
[FromQuery] int? idSeccionAfectada, [FromQuery] int? idPublicacionAfectada) // Nuevos filtros
{
if (!TienePermiso(PermisoVerAuditoria)) return Forbid();
try
{
var historial = await _publiSeccionService.ObtenerHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idSeccionAfectada, idPublicacionAfectada);
return Ok(historial ?? Enumerable.Empty<PubliSeccionHistorialDto>());
}
catch (Exception ex)
{
_logger.LogError(ex, "Error obteniendo historial de Secciones de Publicación.");
return StatusCode(500, "Error interno al obtener historial de Secciones de Publicación.");
}
}
[HttpGet("precios-publicacion-maestro")]
[ProducesResponseType(typeof(IEnumerable<PrecioHistorialDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetHistorialPreciosMaestro(
[FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta,
[FromQuery] int? idUsuarioModifico, [FromQuery] string? tipoModificacion,
[FromQuery] int? idPrecioAfectado, [FromQuery] int? idPublicacionAfectada)
{
if (!TienePermiso(PermisoVerAuditoria)) return Forbid();
try
{
var historial = await _precioService.ObtenerHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idPrecioAfectado, idPublicacionAfectada);
return Ok(historial ?? Enumerable.Empty<PrecioHistorialDto>());
}
catch (Exception ex)
{
_logger.LogError(ex, "Error obteniendo historial de Precios de Publicación.");
return StatusCode(500, "Error interno al obtener historial de Precios de Publicación.");
}
}
[HttpGet("recargos-zona-maestro")]
[ProducesResponseType(typeof(IEnumerable<RecargoZonaHistorialDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetHistorialRecargosZonaMaestro(
[FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta,
[FromQuery] int? idUsuarioModifico, [FromQuery] string? tipoModificacion,
[FromQuery] int? idRecargoAfectado, [FromQuery] int? idPublicacionAfectada, [FromQuery] int? idZonaAfectada)
{
if (!TienePermiso(PermisoVerAuditoria)) return Forbid();
try
{
var historial = await _recargoZonaService.ObtenerHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idRecargoAfectado, idPublicacionAfectada, idZonaAfectada);
return Ok(historial ?? Enumerable.Empty<RecargoZonaHistorialDto>());
}
catch (Exception ex)
{
_logger.LogError(ex, "Error obteniendo historial de Recargos por Zona.");
return StatusCode(500, "Error interno al obtener historial de Recargos por Zona.");
}
}
[HttpGet("porc-pago-dist-maestro")]
[ProducesResponseType(typeof(IEnumerable<PorcPagoHistorialDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetHistorialPorcPagoDistMaestro(
[FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta,
[FromQuery] int? idUsuarioModifico, [FromQuery] string? tipoModificacion,
[FromQuery] int? idPorcentajeAfectado, [FromQuery] int? idPublicacionAfectada, [FromQuery] int? idDistribuidorAfectado)
{
if (!TienePermiso(PermisoVerAuditoria)) return Forbid();
try
{
var historial = await _porcPagoService.ObtenerHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idPorcentajeAfectado, idPublicacionAfectada, idDistribuidorAfectado);
return Ok(historial ?? Enumerable.Empty<PorcPagoHistorialDto>());
}
catch (Exception ex)
{
_logger.LogError(ex, "Error obteniendo historial de Porcentajes de Pago (Dist).");
return StatusCode(500, "Error interno al obtener historial de Porcentajes de Pago (Dist).");
}
}
[HttpGet("porc-mon-canilla-maestro")]
[ProducesResponseType(typeof(IEnumerable<PorcMonCanillaHistorialDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetHistorialPorcMonCanillaMaestro(
[FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta,
[FromQuery] int? idUsuarioModifico, [FromQuery] string? tipoModificacion,
[FromQuery] int? idPorcMonAfectado, [FromQuery] int? idPublicacionAfectada, [FromQuery] int? idCanillaAfectado)
{
if (!TienePermiso(PermisoVerAuditoria)) return Forbid();
try
{
var historial = await _porcMonCanillaService.ObtenerHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idPorcMonAfectado, idPublicacionAfectada, idCanillaAfectado);
return Ok(historial ?? Enumerable.Empty<PorcMonCanillaHistorialDto>());
}
catch (Exception ex)
{
_logger.LogError(ex, "Error obteniendo historial de Porc/Mon Canillita.");
return StatusCode(500, "Error interno al obtener historial de Porc/Mon Canillita.");
}
}
[HttpGet("control-devoluciones-maestro")]
[ProducesResponseType(typeof(IEnumerable<ControlDevolucionesHistorialDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetHistorialControlDevolucionesMaestro(
[FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta,
[FromQuery] int? idUsuarioModifico, [FromQuery] string? tipoModificacion,
[FromQuery] int? idControlAfectado, [FromQuery] int? idEmpresaAfectada, [FromQuery] DateTime? fechaAfectada)
{
if (!TienePermiso(PermisoVerAuditoria)) return Forbid();
try
{
var historial = await _controlDevolucionesService.ObtenerHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idControlAfectado, idEmpresaAfectada, fechaAfectada);
return Ok(historial ?? Enumerable.Empty<ControlDevolucionesHistorialDto>());
}
catch (Exception ex)
{
_logger.LogError(ex, "Error obteniendo historial de Control de Devoluciones.");
return StatusCode(500, "Error interno al obtener historial de Control de Devoluciones.");
}
}
[HttpGet("tipos-bobina-maestro")]
[ProducesResponseType(typeof(IEnumerable<TipoBobinaHistorialDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetHistorialTiposBobinaMaestro(
[FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta,
[FromQuery] int? idUsuarioModifico, [FromQuery] string? tipoModificacion,
[FromQuery] int? idTipoBobinaAfectado)
{
if (!TienePermiso(PermisoVerAuditoria)) return Forbid();
try
{
var historial = await _tipoBobinaService.ObtenerHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idTipoBobinaAfectado);
return Ok(historial ?? Enumerable.Empty<TipoBobinaHistorialDto>());
}
catch (Exception ex)
{
_logger.LogError(ex, "Error obteniendo historial de Tipos de Bobina.");
return StatusCode(500, "Error interno al obtener historial de Tipos de Bobina.");
}
}
[HttpGet("estados-bobina-maestro")]
[ProducesResponseType(typeof(IEnumerable<EstadoBobinaHistorialDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetHistorialEstadosBobinaMaestro(
[FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta,
[FromQuery] int? idUsuarioModifico, [FromQuery] string? tipoModificacion,
[FromQuery] int? idEstadoBobinaAfectado)
{
if (!TienePermiso(PermisoVerAuditoria)) return Forbid();
try
{
var historial = await _estadoBobinaService.ObtenerHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idEstadoBobinaAfectado);
return Ok(historial ?? Enumerable.Empty<EstadoBobinaHistorialDto>());
}
catch (Exception ex)
{
_logger.LogError(ex, "Error obteniendo historial de Estados de Bobina.");
return StatusCode(500, "Error interno al obtener historial de Estados de Bobina.");
}
}
[HttpGet("plantas-impresion-maestro")]
[ProducesResponseType(typeof(IEnumerable<PlantaHistorialDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetHistorialPlantasMaestro(
[FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta,
[FromQuery] int? idUsuarioModifico, [FromQuery] string? tipoModificacion,
[FromQuery] int? idPlantaAfectada)
{
if (!TienePermiso(PermisoVerAuditoria)) return Forbid();
try
{
var historial = await _plantaService.ObtenerHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idPlantaAfectada);
return Ok(historial ?? Enumerable.Empty<PlantaHistorialDto>());
}
catch (Exception ex)
{
_logger.LogError(ex, "Error obteniendo historial de Plantas de Impresión.");
return StatusCode(500, "Error interno al obtener historial de Plantas de Impresión.");
}
}
[HttpGet("stock-bobinas-maestro")]
[ProducesResponseType(typeof(IEnumerable<StockBobinaHistorialDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetHistorialStockBobinasMaestro(
[FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta,
[FromQuery] int? idUsuarioModifico, [FromQuery] string? tipoModificacion,
[FromQuery] int? idBobinaAfectada, [FromQuery] int? idTipoBobinaFiltro,
[FromQuery] int? idPlantaFiltro, [FromQuery] int? idEstadoBobinaFiltro)
{
if (!TienePermiso(PermisoVerAuditoria)) return Forbid();
try
{
var historial = await _stockBobinaService.ObtenerHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idBobinaAfectada, idTipoBobinaFiltro, idPlantaFiltro, idEstadoBobinaFiltro);
return Ok(historial ?? Enumerable.Empty<StockBobinaHistorialDto>());
}
catch (Exception ex)
{
_logger.LogError(ex, "Error obteniendo historial de Stock de Bobinas.");
return StatusCode(500, "Error interno al obtener historial de Stock de Bobinas.");
}
}
[HttpGet("reg-tiradas-maestro")]
[ProducesResponseType(typeof(IEnumerable<RegTiradaHistorialDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetHistorialRegTiradasMaestro(
[FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta,
[FromQuery] int? idUsuarioModifico, [FromQuery] string? tipoModificacion,
[FromQuery] int? idRegistroAfectado, [FromQuery] int? idPublicacionFiltro,
[FromQuery] int? idPlantaFiltro, [FromQuery] DateTime? fechaTiradaFiltro)
{
if (!TienePermiso(PermisoVerAuditoria)) return Forbid();
try
{
var historial = await _tiradaService.ObtenerRegTiradasHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idRegistroAfectado, idPublicacionFiltro, idPlantaFiltro, fechaTiradaFiltro);
return Ok(historial ?? Enumerable.Empty<RegTiradaHistorialDto>());
}
catch (Exception ex)
{
_logger.LogError(ex, "Error obteniendo historial de Registro de Tiradas.");
return StatusCode(500, "Error interno al obtener historial de Registro de Tiradas.");
}
}
[HttpGet("reg-secciones-tirada-maestro")]
[ProducesResponseType(typeof(IEnumerable<RegSeccionTiradaHistorialDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetHistorialRegSeccionesTiradaMaestro(
[FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta,
[FromQuery] int? idUsuarioModifico, [FromQuery] string? tipoModificacion,
[FromQuery] int? idTiradaAfectada, [FromQuery] int? idPublicacionFiltro,
[FromQuery] int? idSeccionFiltro, [FromQuery] int? idPlantaFiltro, [FromQuery] DateTime? fechaTiradaFiltro)
{
if (!TienePermiso(PermisoVerAuditoria)) return Forbid();
try
{
var historial = await _tiradaService.ObtenerRegSeccionesTiradaHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idTiradaAfectada, idPublicacionFiltro, idSeccionFiltro, idPlantaFiltro, fechaTiradaFiltro);
return Ok(historial ?? Enumerable.Empty<RegSeccionTiradaHistorialDto>());
}
catch (Exception ex)
{
_logger.LogError(ex, "Error obteniendo historial de Secciones de Tirada.");
return StatusCode(500, "Error interno al obtener historial de Secciones de Tirada.");
}
}
[HttpGet("perfiles-maestro")]
[ProducesResponseType(typeof(IEnumerable<PerfilHistorialDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetHistorialPerfilesMaestro(
[FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta,
[FromQuery] int? idUsuarioModifico, [FromQuery] string? tipoModificacion,
[FromQuery] int? idPerfilAfectado)
{
if (!TienePermiso(PermisoVerAuditoria)) return Forbid();
try
{
var historial = await _perfilService.ObtenerHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idPerfilAfectado);
return Ok(historial ?? Enumerable.Empty<PerfilHistorialDto>());
}
catch (Exception ex)
{
_logger.LogError(ex, "Error obteniendo historial de Perfiles (Maestro).");
return StatusCode(500, "Error interno al obtener historial de Perfiles (Maestro).");
}
}
[HttpGet("permisos-maestro")]
[ProducesResponseType(typeof(IEnumerable<PermisoHistorialDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetHistorialPermisosMaestro(
[FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta,
[FromQuery] int? idUsuarioModifico, [FromQuery] string? tipoModificacion,
[FromQuery] int? idPermisoAfectado)
{
if (!TienePermiso(PermisoVerAuditoria)) return Forbid();
try
{
var historial = await _permisoService.ObtenerHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idPermisoAfectado);
return Ok(historial ?? Enumerable.Empty<PermisoHistorialDto>());
}
catch (Exception ex)
{
_logger.LogError(ex, "Error obteniendo historial de Permisos (Maestro).");
return StatusCode(500, "Error interno al obtener historial de Permisos (Maestro).");
}
}
[HttpGet("permisos-perfiles-historial")]
[ProducesResponseType(typeof(IEnumerable<PermisosPerfilesHistorialDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetHistorialPermisosPerfiles(
[FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta,
[FromQuery] int? idUsuarioModifico, [FromQuery] string? tipoModificacion,
[FromQuery] int? idPerfilAfectado, [FromQuery] int? idPermisoAfectado)
{
if (!TienePermiso(PermisoVerAuditoria)) return Forbid();
try
{
var historial = await _perfilService.ObtenerPermisosAsignadosHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idPerfilAfectado, idPermisoAfectado);
return Ok(historial ?? Enumerable.Empty<PermisosPerfilesHistorialDto>());
}
catch (Exception ex)
{
_logger.LogError(ex, "Error obteniendo historial de Asignación de Permisos.");
return StatusCode(500, "Error interno al obtener historial de Asignación de Permisos.");
}
}
[HttpGet("cambios-parada-canilla")]
[ProducesResponseType(typeof(IEnumerable<CambioParadaHistorialDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetHistorialCambiosParada(
[FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta,
[FromQuery] int? idUsuarioModifico, [FromQuery] string? tipoModificacion,
[FromQuery] int? idCanillaAfectado)
{
if (!TienePermiso(PermisoVerAuditoria)) return Forbid();
try
{
var historial = await _cambioParadaService.ObtenerCambiosParadaHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idCanillaAfectado);
return Ok(historial ?? Enumerable.Empty<CambioParadaHistorialDto>());
}
catch (Exception ex)
{
_logger.LogError(ex, "Error obteniendo historial de Cambios de Parada.");
return StatusCode(500, "Error interno al obtener historial de Cambios de Parada.");
}
}
}
}

View File

@@ -0,0 +1,40 @@
using GestionIntegral.Api.Dtos.Comunicaciones;
using GestionIntegral.Api.Services.Comunicaciones;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace GestionIntegral.Api.Controllers.Comunicaciones
{
[Route("api/lotes-envio")]
[ApiController]
[Authorize]
public class LotesEnvioController : ControllerBase
{
private readonly IEmailLogService _emailLogService;
public LotesEnvioController(IEmailLogService emailLogService)
{
_emailLogService = emailLogService;
}
// GET: api/lotes-envio/123/detalles
[HttpGet("{idLote:int}/detalles")]
[ProducesResponseType(typeof(IEnumerable<EmailLogDto>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetDetallesLote(int idLote)
{
// Reutilizamos un permiso existente, ya que esta es una función de auditoría relacionada.
var tienePermiso = User.IsInRole("SuperAdmin") || User.HasClaim(c => c.Type == "permission" && c.Value == "SU006");
if (!tienePermiso)
{
return Forbid();
}
var detalles = await _emailLogService.ObtenerDetallesPorLoteId(idLote);
// Devolvemos OK con un array vacío si no hay resultados, el frontend lo manejará.
return Ok(detalles);
}
}
}

View File

@@ -0,0 +1,109 @@
using GestionIntegral.Api.Dtos.Contables;
using GestionIntegral.Api.Services.Contables;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;
namespace GestionIntegral.Api.Controllers.Contables
{
[Route("api/saldos")]
[ApiController]
[Authorize] // Requiere autenticación para todos los endpoints
public class SaldosController : ControllerBase
{
private readonly ISaldoService _saldoService;
private readonly ILogger<SaldosController> _logger;
// Define un permiso específico para ver saldos, y otro para ajustarlos (SuperAdmin implícito)
private const string PermisoVerSaldos = "CS001"; // Ejemplo: Cuentas Saldos Ver
private const string PermisoAjustarSaldos = "CS002"; // Ejemplo: Cuentas Saldos Ajustar (o solo SuperAdmin)
public SaldosController(ISaldoService saldoService, ILogger<SaldosController> logger)
{
_saldoService = saldoService;
_logger = logger;
}
private bool TienePermiso(string codAccRequerido)
{
if (User.IsInRole("SuperAdmin")) return true;
return User.HasClaim(c => c.Type == "permission" && c.Value == codAccRequerido);
}
private int? GetCurrentUserId()
{
if (int.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub"), out int userId)) return userId;
_logger.LogWarning("No se pudo obtener el UserId del token JWT en SaldosController.");
return null;
}
// GET: api/saldos
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<SaldoGestionDto>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> GetSaldosGestion(
[FromQuery] string? destino,
[FromQuery] int? idDestino,
[FromQuery] int? idEmpresa)
{
if (!TienePermiso(PermisoVerSaldos)) // Usar el nuevo permiso
{
_logger.LogWarning("Acceso denegado a GetSaldosGestion para Usuario ID {userId}", GetCurrentUserId() ?? 0);
return Forbid();
}
try
{
var saldos = await _saldoService.ObtenerSaldosParaGestionAsync(destino, idDestino, idEmpresa);
return Ok(saldos);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error al obtener saldos para gestión.");
return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al obtener saldos.");
}
}
// POST: api/saldos/ajustar
[HttpPost("ajustar")]
[ProducesResponseType(typeof(SaldoGestionDto), StatusCodes.Status200OK)] // Devuelve el saldo actualizado
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)] // Solo SuperAdmin o con permiso específico
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> AjustarSaldoManualmente([FromBody] AjusteSaldoRequestDto ajusteDto)
{
// Esta operación debería ser MUY restringida. Solo SuperAdmin o un permiso muy específico.
if (!User.IsInRole("SuperAdmin") && !TienePermiso(PermisoAjustarSaldos))
{
_logger.LogWarning("Intento no autorizado de ajustar saldo por Usuario ID {userId}", GetCurrentUserId() ?? 0);
return Forbid("No tiene permisos para realizar ajustes manuales de saldo.");
}
if (!ModelState.IsValid) return BadRequest(ModelState);
var idUsuario = GetCurrentUserId();
if (idUsuario == null) return Unauthorized("No se pudo identificar al usuario.");
try
{
var (exito, error, saldoActualizado) = await _saldoService.RealizarAjusteManualSaldoAsync(ajusteDto, idUsuario.Value);
if (!exito)
{
if (error != null && error.Contains("No se encontró un saldo existente"))
return NotFound(new { message = error });
return BadRequest(new { message = error ?? "Error desconocido al ajustar el saldo." });
}
return Ok(saldoActualizado);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error crítico al ajustar saldo manualmente.");
return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al procesar el ajuste de saldo.");
}
}
}
}

View File

@@ -0,0 +1,129 @@
using GestionIntegral.Api.Dtos.Distribucion;
using GestionIntegral.Api.Services.Distribucion;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;
namespace GestionIntegral.Api.Controllers.Distribucion
{
[Route("api/canillas/{idCanilla}/paradas")] // Anidado bajo canillas
[ApiController]
[Authorize]
public class CambiosParadaController : ControllerBase
{
private readonly ICambioParadaService _paradaService;
private readonly ILogger<CambiosParadaController> _logger;
private const string PermisoGestionarParadas = "CG007";
public CambiosParadaController(ICambioParadaService paradaService, ILogger<CambiosParadaController> logger)
{
_paradaService = paradaService;
_logger = logger;
}
private bool TienePermiso(string codAcc) => User.IsInRole("SuperAdmin") || User.HasClaim(c => c.Type == "permission" && c.Value == codAcc);
private int? GetCurrentUserId()
{
if (int.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub"), out int userId)) return userId;
_logger.LogWarning("No se pudo obtener el UserId del token JWT en CambiosParadaController.");
return null;
}
// GET: api/canillas/{idCanilla}/paradas
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<CambioParadaDto>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> GetParadasPorCanilla(int idCanilla)
{
// Podría usarse CG001 (Ver Canilla) o CG007 para ver el historial de paradas
if (!TienePermiso("CG001") && !TienePermiso(PermisoGestionarParadas)) return Forbid();
var paradas = await _paradaService.ObtenerPorCanillaAsync(idCanilla);
return Ok(paradas);
}
// POST: api/canillas/{idCanilla}/paradas
[HttpPost]
[ProducesResponseType(typeof(CambioParadaDto), StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> CreateParada(int idCanilla, [FromBody] CreateCambioParadaDto createDto)
{
if (!TienePermiso(PermisoGestionarParadas)) return Forbid();
if (!ModelState.IsValid) return BadRequest(ModelState);
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
var (paradaDto, error) = await _paradaService.CrearNuevaParadaAsync(idCanilla, createDto, userId.Value);
if (error != null) return BadRequest(new { message = error });
if (paradaDto == null) return StatusCode(StatusCodes.Status500InternalServerError, "Error al crear la parada.");
// La ruta para "GetById" podría estar en otro controlador si decides separar los endpoints de "paradas individuales"
// Por ahora, asumimos que habrá un endpoint para obtener una parada por su ID de registro.
return CreatedAtAction(nameof(GetParadaById), new { idCanilla = idCanilla, idRegistroParada = paradaDto.IdRegistro }, paradaDto);
}
// GET: api/canillas/{idCanilla}/paradas/{idRegistroParada} (o api/paradas/{idRegistroParada})
// Este endpoint es opcional, pero útil si necesitas obtener una parada específica por su ID de registro
[HttpGet("{idRegistroParada:int}", Name = "GetParadaById")]
[ProducesResponseType(typeof(CambioParadaDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetParadaById(int idCanilla, int idRegistroParada) // idCanilla es parte de la ruta base
{
if (!TienePermiso("CG001") && !TienePermiso(PermisoGestionarParadas)) return Forbid();
var parada = await _paradaService.ObtenerPorIdAsync(idRegistroParada);
if (parada == null || parada.IdCanilla != idCanilla) return NotFound(new { message = "Registro de parada no encontrado para este canillita."});
return Ok(parada);
}
// PUT: api/paradas/{idRegistroParada}/cerrar (Ruta ejemplo para cerrar una parada)
// O podrías usar PUT api/canillas/{idCanilla}/paradas/{idRegistroParada} y que el DTO solo tenga VigenciaH
[HttpPut("~/api/paradas/{idRegistroParada}/cerrar")] // Ruta a nivel raíz para paradas si se edita por IdRegistro
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> CerrarVigenciaParada(int idRegistroParada, [FromBody] UpdateCambioParadaDto updateDto)
{
if (!TienePermiso(PermisoGestionarParadas)) return Forbid();
if (!ModelState.IsValid) return BadRequest(ModelState);
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
var (exito, error) = await _paradaService.CerrarParadaAsync(idRegistroParada, updateDto, userId.Value);
if (!exito)
{
if (error != null && error.Contains("no encontrado")) return NotFound(new { message = error });
return BadRequest(new { message = error });
}
return NoContent();
}
// DELETE: api/paradas/{idRegistroParada}
[HttpDelete("~/api/paradas/{idRegistroParada}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> DeleteParada(int idRegistroParada)
{
if (!TienePermiso(PermisoGestionarParadas)) return Forbid();
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
var (exito, error) = await _paradaService.EliminarParadaAsync(idRegistroParada, userId.Value);
if (!exito)
{
if (error != null && error.Contains("no encontrado")) return NotFound(new { message = error });
return BadRequest(new { message = error });
}
return NoContent();
}
}
}

View File

@@ -47,13 +47,22 @@ namespace GestionIntegral.Api.Controllers.Distribucion
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<CanillaDto>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> GetAllCanillas([FromQuery] string? nomApe, [FromQuery] int? legajo, [FromQuery] bool? soloActivos = true)
public async Task<IActionResult> GetAllCanillas([FromQuery] string? nomApe, [FromQuery] int? legajo, [FromQuery] bool? esAccionista, [FromQuery] bool? soloActivos = true)
{
if (!TienePermiso(PermisoVer)) return Forbid();
var canillas = await _canillaService.ObtenerTodosAsync(nomApe, legajo, soloActivos);
return Ok(canillas);
var canillitas = await _canillaService.ObtenerTodosAsync(nomApe, legajo, soloActivos, esAccionista);
return Ok(canillitas);
}
[HttpGet("dropdown")]
[ProducesResponseType(typeof(IEnumerable<CanillaDropdownDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetAllDropdownCanillas([FromQuery] bool? esAccionista, [FromQuery] bool? soloActivos = true)
{
var canillitas = await _canillaService.ObtenerTodosDropdownAsync(esAccionista, soloActivos);
return Ok(canillitas);
}
// GET: api/canillas/{id}
[HttpGet("{id:int}", Name = "GetCanillaById")]
[ProducesResponseType(typeof(CanillaDto), StatusCodes.Status200OK)]
@@ -117,7 +126,7 @@ namespace GestionIntegral.Api.Controllers.Distribucion
public async Task<IActionResult> ToggleBajaCanilla(int id, [FromBody] ToggleBajaCanillaDto bajaDto)
{
if (!TienePermiso(PermisoBaja)) return Forbid();
if (!ModelState.IsValid) return BadRequest(ModelState);
if (!ModelState.IsValid) return BadRequest(ModelState);
var idUsuario = GetCurrentUserId();
if (idUsuario == null) return Unauthorized();

View File

@@ -47,6 +47,15 @@ namespace GestionIntegral.Api.Controllers.Distribucion
return Ok(distribuidores);
}
[HttpGet("dropdown")]
[ProducesResponseType(typeof(IEnumerable<DistribuidorDropdownDto>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> GetAllDropdownDistribuidores()
{
var distribuidores = await _distribuidorService.GetAllDropdownAsync();
return Ok(distribuidores);
}
[HttpGet("{id:int}", Name = "GetDistribuidorById")]
[ProducesResponseType(typeof(DistribuidorDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
@@ -59,6 +68,17 @@ namespace GestionIntegral.Api.Controllers.Distribucion
return Ok(distribuidor);
}
[HttpGet("{id:int}/lookup", Name = "GetDistribuidorLookupById")]
[ProducesResponseType(typeof(DistribuidorLookupDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> ObtenerLookupPorIdAsync(int id)
{
var distribuidor = await _distribuidorService.ObtenerLookupPorIdAsync(id);
if (distribuidor == null) return NotFound();
return Ok(distribuidor);
}
[HttpPost]
[ProducesResponseType(typeof(DistribuidorDto), StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]

View File

@@ -74,6 +74,25 @@ namespace GestionIntegral.Api.Controllers // Ajusta el namespace si es necesario
}
}
// GET: api/empresas/dropdown
[HttpGet("dropdown")]
[ProducesResponseType(typeof(IEnumerable<EmpresaDropdownDto>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<IActionResult> GetEmpresasDropdown()
{
try
{
var empresas = await _empresaService.ObtenerParaDropdown();
return Ok(empresas);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error al obtener todas las Empresas.");
return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al obtener las empresas.");
}
}
// GET: api/empresas/{id}
// Permiso Requerido: DE001 (Ver Empresas)
[HttpGet("{id:int}", Name = "GetEmpresaById")]
@@ -101,6 +120,29 @@ namespace GestionIntegral.Api.Controllers // Ajusta el namespace si es necesario
}
}
[HttpGet("{id:int}/lookup", Name = "GetEmpresaLookupById")]
[ProducesResponseType(typeof(EmpresaDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<IActionResult> ObtenerLookupPorIdAsync(int id)
{
try
{
var empresa = await _empresaService.ObtenerLookupPorIdAsync(id);
if (empresa == null)
{
return NotFound(new { message = $"Empresa con ID {id} no encontrada." });
}
return Ok(empresa);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error al obtener Empresa por ID: {Id}", id);
return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al obtener la empresa.");
}
}
// POST: api/empresas
// Permiso Requerido: DE002 (Agregar Empresas)
[HttpPost]

View File

@@ -0,0 +1,212 @@
using GestionIntegral.Api.Dtos.Distribucion;
using GestionIntegral.Api.Services.Distribucion;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
namespace GestionIntegral.Api.Controllers.Distribucion
{
[Route("api/novedadescanilla")] // Ruta base más genérica para las novedades
[ApiController]
[Authorize] // Todas las acciones requieren autenticación
public class NovedadesCanillaController : ControllerBase
{
private readonly INovedadCanillaService _novedadService;
private readonly ILogger<NovedadesCanillaController> _logger;
public NovedadesCanillaController(INovedadCanillaService novedadService, ILogger<NovedadesCanillaController> logger)
{
_novedadService = novedadService;
_logger = logger;
}
// --- Helper para verificar permisos ---
private bool TienePermiso(string codAccRequerido)
{
if (User.IsInRole("SuperAdmin")) return true;
return User.HasClaim(c => c.Type == "permission" && c.Value == codAccRequerido);
}
// --- Helper para obtener User ID ---
private int? GetCurrentUserId()
{
var userIdClaim = User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub");
if (int.TryParse(userIdClaim, out int userId))
{
return userId;
}
_logger.LogWarning("No se pudo obtener el UserId del token JWT en NovedadesCanillaController.");
return null;
}
// GET: api/novedadescanilla/porcanilla/{idCanilla}
// Obtiene todas las novedades para un canillita específico, opcionalmente filtrado por fecha.
// Permiso: CG001 (Ver Canillas) o CG006 (Gestionar Novedades).
// Si CG006 es "Permite la Carga/Modificación", entonces CG001 podría ser más apropiado solo para ver.
// Vamos a usar CG001 para ver. Si se quiere más granularidad, se puede crear un permiso "Ver Novedades".
[HttpGet("porcanilla/{idCanilla:int}")]
[ProducesResponseType(typeof(IEnumerable<NovedadCanillaDto>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<IActionResult> GetNovedadesPorCanilla(int idCanilla, [FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta)
{
if (!TienePermiso("CG001") && !TienePermiso("CG006")) // Necesita al menos uno de los dos
{
_logger.LogWarning("Acceso denegado a GetNovedadesPorCanilla para el usuario {UserId} y canillita {IdCanilla}", GetCurrentUserId() ?? 0, idCanilla);
return Forbid();
}
try
{
var novedades = await _novedadService.ObtenerPorCanillaAsync(idCanilla, fechaDesde, fechaHasta);
return Ok(novedades);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error al obtener novedades para Canillita ID: {IdCanilla}", idCanilla);
return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al obtener las novedades.");
}
}
// GET: api/novedadescanilla/{idNovedad}
// Obtiene una novedad específica por su ID.
// Permiso: CG001 o CG006
[HttpGet("{idNovedad:int}", Name = "GetNovedadCanillaById")]
[ProducesResponseType(typeof(NovedadCanillaDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<IActionResult> GetNovedadCanillaById(int idNovedad)
{
if (!TienePermiso("CG001") && !TienePermiso("CG006")) return Forbid();
try
{
var novedad = await _novedadService.ObtenerPorIdAsync(idNovedad);
if (novedad == null)
{
return NotFound(new { message = $"Novedad con ID {idNovedad} no encontrada." });
}
return Ok(novedad);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error al obtener NovedadCanilla por ID: {IdNovedad}", idNovedad);
return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al obtener la novedad.");
}
}
// POST: api/novedadescanilla
// Crea una nueva novedad. El IdCanilla viene en el DTO.
// Permiso: CG006 (Permite la Carga/Modificación de Novedades)
[HttpPost]
[ProducesResponseType(typeof(NovedadCanillaDto), StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<IActionResult> CreateNovedadCanilla([FromBody] CreateNovedadCanillaDto createDto)
{
if (!TienePermiso("CG006")) return Forbid();
if (!ModelState.IsValid) return BadRequest(ModelState);
var idUsuario = GetCurrentUserId();
if (idUsuario == null) return Unauthorized("No se pudo obtener el ID del usuario del token.");
try
{
var (novedadCreada, error) = await _novedadService.CrearAsync(createDto, idUsuario.Value);
if (error != null) return BadRequest(new { message = error });
if (novedadCreada == null) return StatusCode(StatusCodes.Status500InternalServerError, "Error al crear la novedad.");
// Devuelve la ruta al recurso creado y el recurso mismo
return CreatedAtRoute("GetNovedadCanillaById", new { idNovedad = novedadCreada.IdNovedad }, novedadCreada);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error al crear NovedadCanilla para Canilla ID: {IdCanilla} por Usuario ID: {UsuarioId}", createDto.IdCanilla, idUsuario);
return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al crear la novedad.");
}
}
// PUT: api/novedadescanilla/{idNovedad}
// Actualiza una novedad existente.
// Permiso: CG006
[HttpPut("{idNovedad:int}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<IActionResult> UpdateNovedadCanilla(int idNovedad, [FromBody] UpdateNovedadCanillaDto updateDto)
{
if (!TienePermiso("CG006")) return Forbid();
if (!ModelState.IsValid) return BadRequest(ModelState);
var idUsuario = GetCurrentUserId();
if (idUsuario == null) return Unauthorized("No se pudo obtener el ID del usuario del token.");
try
{
var (exito, error) = await _novedadService.ActualizarAsync(idNovedad, updateDto, idUsuario.Value);
if (!exito)
{
if (error == "Novedad no encontrada.") return NotFound(new { message = error });
return BadRequest(new { message = error });
}
return NoContent();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error al actualizar NovedadCanilla ID: {IdNovedad} por Usuario ID: {UsuarioId}", idNovedad, idUsuario);
return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al actualizar la novedad.");
}
}
// DELETE: api/novedadescanilla/{idNovedad}
// Elimina una novedad.
// Permiso: CG006 (Asumiendo que el mismo permiso para Carga/Modificación incluye eliminación)
// Si la eliminación es un permiso separado (ej: CG00X), ajústalo.
[HttpDelete("{idNovedad:int}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<IActionResult> DeleteNovedadCanilla(int idNovedad)
{
if (!TienePermiso("CG006")) return Forbid();
var idUsuario = GetCurrentUserId();
if (idUsuario == null) return Unauthorized("No se pudo obtener el ID del usuario del token.");
try
{
var (exito, error) = await _novedadService.EliminarAsync(idNovedad, idUsuario.Value);
if (!exito)
{
if (error == "Novedad no encontrada.") return NotFound(new { message = error });
return BadRequest(new { message = error }); // Podría ser otro error, como "no se pudo eliminar"
}
return NoContent();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error al eliminar NovedadCanilla ID: {IdNovedad} por Usuario ID: {UsuarioId}", idNovedad, idUsuario);
return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al eliminar la novedad.");
}
}
}
}

View File

@@ -64,6 +64,23 @@ namespace GestionIntegral.Api.Controllers.Distribucion
}
}
[HttpGet("dropdown")]
[ProducesResponseType(typeof(IEnumerable<OtroDestinoDropdownDto>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<IActionResult> GetAllOtrosDestinosDropdown()
{
try
{
var destinos = await _otroDestinoService.ObtenerTodosDropdownAsync();
return Ok(destinos);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error al obtener Otros Destinos para dropdown.");
return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al obtener la lista de destinos.");
}
}
// GET: api/otrosdestinos/{id}
[HttpGet("{id:int}", Name = "GetOtroDestinoById")]
[ProducesResponseType(typeof(OtroDestinoDto), StatusCodes.Status200OK)]

View File

@@ -38,16 +38,37 @@ namespace GestionIntegral.Api.Controllers.Distribucion
return null;
}
// GET: api/publicaciones
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<PublicacionDto>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> GetAllPublicaciones([FromQuery] string? nombre, [FromQuery] int? idEmpresa, [FromQuery] bool? soloHabilitadas = true)
public async Task<IActionResult> GetAllPublicaciones([FromQuery] string? nombre, [FromQuery] int? idEmpresa, [FromQuery] bool? soloHabilitadas)
{
if (!TienePermiso(PermisoVer)) return Forbid();
var publicaciones = await _publicacionService.ObtenerTodasAsync(nombre, idEmpresa, soloHabilitadas);
return Ok(publicaciones);
}
// GET: api/publicaciones/dropdown
[HttpGet("dropdown")]
[ProducesResponseType(typeof(IEnumerable<PublicacionDropdownDto>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
// No se verifica permiso DP001, solo requiere autenticación general ([Authorize] del controlador)
public async Task<IActionResult> GetPublicacionesForDropdown([FromQuery] bool? soloHabilitadas)
{
try
{
var publicaciones = await _publicacionService.ObtenerParaDropdownAsync(soloHabilitadas);
return Ok(publicaciones);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error al obtener publicaciones para dropdown.");
return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al obtener publicaciones para selección.");
}
}
// GET: api/publicaciones/{id}
[HttpGet("{id:int}", Name = "GetPublicacionById")]
[ProducesResponseType(typeof(PublicacionDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
@@ -60,6 +81,7 @@ namespace GestionIntegral.Api.Controllers.Distribucion
return Ok(publicacion);
}
// POST: api/publicaciones
[HttpPost]
[ProducesResponseType(typeof(PublicacionDto), StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
@@ -77,6 +99,7 @@ namespace GestionIntegral.Api.Controllers.Distribucion
return CreatedAtRoute("GetPublicacionById", new { id = dto.IdPublicacion }, dto);
}
// PUT: api/publicaciones/{id}
[HttpPut("{id:int}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
@@ -98,6 +121,7 @@ namespace GestionIntegral.Api.Controllers.Distribucion
return NoContent();
}
// DELETE: api/publicaciones/{id}
[HttpDelete("{id:int}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
@@ -118,7 +142,7 @@ namespace GestionIntegral.Api.Controllers.Distribucion
return NoContent();
}
// Endpoint para obtener las configuraciones de días para una publicación
// Endpoint para obtener la configuración de días de publicación para una publicación específica
[HttpGet("{idPublicacion:int}/dias-semana")]
[ProducesResponseType(typeof(IEnumerable<PublicacionDiaSemanaDto>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]

View File

@@ -67,6 +67,23 @@ namespace GestionIntegral.Api.Controllers.Impresion
}
}
// GET: api/estadosbobina/dropdown
[HttpGet("dropdown")]
[ProducesResponseType(typeof(IEnumerable<EstadoBobinaDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetAllDropdownEstadosBobina()
{
try
{
var estados = await _estadoBobinaService.ObtenerTodosDropdownAsync();
return Ok(estados);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error al obtener todos los Estados de Bobina.");
return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al obtener los estados de bobina.");
}
}
// GET: api/estadosbobina/{id}
[HttpGet("{id:int}", Name = "GetEstadoBobinaById")]
[ProducesResponseType(typeof(EstadoBobinaDto), StatusCodes.Status200OK)]

View File

@@ -71,6 +71,25 @@ namespace GestionIntegral.Api.Controllers.Impresion
}
}
// GET: api/plantas/dropdown
[HttpGet("dropdown")]
[ProducesResponseType(typeof(IEnumerable<PlantaDropdownDto>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
// NO chequeo TienePermiso("IP001")(requiere autenticación)
public async Task<IActionResult> GetPlantasForDropdown()
{
try
{
var plantas = await _plantaService.ObtenerParaDropdownAsync();
return Ok(plantas);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error al obtener plantas para dropdown.");
return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al obtener plantas para selección.");
}
}
// GET: api/plantas/{id}
// Permiso: IP001 (Ver Plantas)
[HttpGet("{id:int}", Name = "GetPlantaById")]

View File

@@ -124,11 +124,15 @@ namespace GestionIntegral.Api.Controllers.Impresion
public async Task<IActionResult> CambiarEstadoBobina(int idBobina, [FromBody] CambiarEstadoBobinaDto cambiarEstadoDto)
{
if (!TienePermiso(PermisoCambiarEstado)) return Forbid();
if (!ModelState.IsValid) return BadRequest(ModelState);
if (!ModelState.IsValid) return BadRequest(ModelState); // Validaciones de DTO (Required, Range, etc.)
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
// Validación adicional en el controlador para el caso "En Uso"
// La validación de que IdPublicacion/IdSeccion son requeridos para estado "En Uso"
// ahora está más robusta en el servicio. Se puede quitar del controlador
// si se prefiere que el servicio sea la única fuente de verdad para esa lógica.
// Si la mantenés acá, es una validación temprana.
/*
if (cambiarEstadoDto.NuevoEstadoId == 2) // Asumiendo 2 = En Uso
{
if (!cambiarEstadoDto.IdPublicacion.HasValue || cambiarEstadoDto.IdPublicacion.Value <= 0)
@@ -136,12 +140,13 @@ namespace GestionIntegral.Api.Controllers.Impresion
if (!cambiarEstadoDto.IdSeccion.HasValue || cambiarEstadoDto.IdSeccion.Value <=0)
return BadRequest(new { message = "Se requiere IdSeccion para el estado 'En Uso'."});
}
*/
var (exito, error) = await _stockBobinaService.CambiarEstadoBobinaAsync(idBobina, cambiarEstadoDto, userId.Value);
if (!exito)
{
if (error == "Bobina no encontrada.") return NotFound(new { message = error });
// Otros errores específicos del servicio (ej. flujo de estado no permitido) vienen como BadRequest
return BadRequest(new { message = error });
}
return NoContent();

View File

@@ -62,6 +62,25 @@ namespace GestionIntegral.Api.Controllers.Impresion
}
}
// GET: api/tiposbobina/dropdown
[HttpGet("dropdown")]
[ProducesResponseType(typeof(IEnumerable<TipoBobinaDto>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<IActionResult> GetAllTiposBobina()
{
try
{
var tiposBobina = await _tipoBobinaService.ObtenerTodosDropdownAsync();
return Ok(tiposBobina);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error al obtener todos los Tipos de Bobina.");
return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al obtener los tipos de bobina.");
}
}
// GET: api/tiposbobina/{id}
// Permiso: IB006 (Ver Tipos Bobinas)
[HttpGet("{id:int}", Name = "GetTipoBobinaById")]

View File

@@ -22,7 +22,8 @@ namespace GestionIntegral.Api.Controllers.Impresion
// Permisos para Tiradas (IT001 a IT003)
private const string PermisoVerTiradas = "IT001";
private const string PermisoRegistrarTirada = "IT002";
private const string PermisoEliminarTirada = "IT003"; // Asumo que se refiere a eliminar una tirada completa (cabecera y detalles)
private const string PermisoEliminarTirada = "IT003";
private const string PermisoModificarTirada = "IT004";
public TiradasController(ITiradaService tiradaService, ILogger<TiradasController> logger)
{
@@ -83,6 +84,43 @@ namespace GestionIntegral.Api.Controllers.Impresion
return StatusCode(StatusCodes.Status201Created, tiradaCreada);
}
[HttpPut]
[ProducesResponseType(typeof(TiradaDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> ModificarTirada(
[FromQuery, BindRequired] DateTime fecha,
[FromQuery, BindRequired] int idPublicacion,
[FromQuery, BindRequired] int idPlanta,
[FromBody] UpdateTiradaRequestDto updateDto)
{
if (!TienePermiso(PermisoModificarTirada)) return Forbid();
if (!ModelState.IsValid) return BadRequest(ModelState);
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
var (tiradaActualizada, error) = await _tiradaService.ModificarTiradaCompletaAsync(fecha, idPublicacion, idPlanta, updateDto, userId.Value);
if (error != null)
{
// Chequear si el error es porque no se encontró la tirada.
if (error.StartsWith("No se encontró la tirada"))
{
return NotFound(new { message = error });
}
return BadRequest(new { message = error });
}
if (tiradaActualizada == null)
{
return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al modificar la tirada.");
}
return Ok(tiradaActualizada);
}
// DELETE: api/tiradas
// Se identifica la tirada a eliminar por su combinación única de Fecha, IdPublicacion, IdPlanta
[HttpDelete]

View File

@@ -0,0 +1,114 @@
using GestionIntegral.Api.Dtos.Reportes.ViewModels;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using System.Globalization;
using System.Linq;
namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
{
public class ComparativaConsumoBobinasDocument : IDocument
{
public ComparativaConsumoBobinasViewModel Model { get; }
public ComparativaConsumoBobinasDocument(ComparativaConsumoBobinasViewModel model)
{
Model = model;
}
public DocumentMetadata GetMetadata() => DocumentMetadata.Default;
public void Compose(IDocumentContainer container)
{
container.Page(page =>
{
page.Margin(1, Unit.Centimetre);
page.DefaultTextStyle(x => x.FontFamily("Arial").FontSize(9));
page.Header().Element(ComposeHeader);
page.Content().Element(ComposeContent);
page.Footer().AlignCenter().Text(x => { x.Span("Página "); x.CurrentPageNumber(); });
});
}
void ComposeHeader(IContainer container)
{
container.Column(column =>
{
column.Spacing(5);
column.Item().AlignCenter().Text("Reporte de Consumo de Bobinas - Comparativa Mensual").SemiBold().FontSize(14);
string subTitle = Model.EsConsolidado ? "Consolidado" : $"Planta: {Model.NombrePlanta}";
column.Item().AlignCenter().Text(subTitle).FontSize(12);
column.Item().PaddingTop(5, Unit.Millimetre).Row(row =>
{
// Le damos una proporción menor al texto de la izquierda (que es corto y fijo).
row.RelativeItem(1).Text(text =>
{
text.Span("Fecha del Reporte: ").SemiBold().FontSize(12);
text.Span(Model.FechaReporte);
});
// Le damos una proporción mayor al texto de la derecha (que es largo y variable).
// El factor "2" significa que tendrá el doble de espacio disponible que el item con factor "1".
row.RelativeItem(2).AlignRight().Text(text =>
{
text.Span("Meses: ").SemiBold().FontSize(12);
text.Span($"{Model.MesA} (Mes A) contra {Model.MesB} (Mes B)").FontSize(12);
});
});
});
}
void ComposeContent(IContainer container)
{
container.PaddingTop(5, Unit.Millimetre).Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn(3); // Tipo Bobina
columns.RelativeColumn(1); columns.RelativeColumn(1); columns.RelativeColumn(1); // Grupo Cantidad
columns.RelativeColumn(1); columns.RelativeColumn(1); columns.RelativeColumn(1); // Grupo Kilos
});
table.Header(header =>
{
// Fila 1 del Encabezado
header.Cell().RowSpan(2).Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignMiddle().Text("Tipo Bobina").SemiBold();
header.Cell().ColumnSpan(3).Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignCenter().Text("Cantidad Bobinas").SemiBold();
header.Cell().ColumnSpan(3).Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignCenter().Text("Kilos Utilizados").SemiBold();
// Fila 2 del Encabezado
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Mes A").SemiBold();
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Mes B").SemiBold();
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Diferencia").SemiBold();
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Kg Mes A").SemiBold();
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Kg Mes B").SemiBold();
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Diferencia").SemiBold();
});
foreach (var item in Model.Detalles.OrderBy(x => x.TipoBobina))
{
table.Cell().Border(1).Padding(3).Text(item.TipoBobina);
table.Cell().Border(1).Padding(3).AlignRight().Text(item.BobinasUtilizadasMesA.ToString("N0"));
table.Cell().Border(1).Padding(3).AlignRight().Text(item.BobinasUtilizadasMesB.ToString("N0"));
table.Cell().Border(1).Padding(3).AlignRight().Text(item.DiferenciaBobinasUtilizadas.ToString("N0"));
table.Cell().Border(1).Padding(3).AlignRight().Text(item.KilosUtilizadosMesA.ToString("N0"));
table.Cell().Border(1).Padding(3).AlignRight().Text(item.KilosUtilizadosMesB.ToString("N0"));
table.Cell().Border(1).Padding(3).AlignRight().Text(item.DiferenciaKilosUtilizados.ToString("N0"));
}
// Fila de Totales
var style = TextStyle.Default.SemiBold();
table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span("Totales").Style(style));
table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(Model.Detalles.Sum(x => x.BobinasUtilizadasMesA).ToString("N0")).Style(style));
table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(Model.Detalles.Sum(x => x.BobinasUtilizadasMesB).ToString("N0")).Style(style));
table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(Model.Detalles.Sum(x => x.DiferenciaBobinasUtilizadas).ToString("N0")).Style(style));
table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(Model.Detalles.Sum(x => x.KilosUtilizadosMesA).ToString("N0")).Style(style));
table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(Model.Detalles.Sum(x => x.KilosUtilizadosMesB).ToString("N0")).Style(style));
table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(Model.Detalles.Sum(x => x.DiferenciaKilosUtilizados).ToString("N0")).Style(style));
});
}
}
}

View File

@@ -0,0 +1,98 @@
using GestionIntegral.Api.Dtos.Reportes.ViewModels;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using System.Linq;
namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
{
public class ConsumoBobinasPublicacionDocument : IDocument
{
public ConsumoBobinasPublicacionViewModel Model { get; }
public ConsumoBobinasPublicacionDocument(ConsumoBobinasPublicacionViewModel model)
{
Model = model;
}
public DocumentMetadata GetMetadata() => DocumentMetadata.Default;
public void Compose(IDocumentContainer container)
{
container.Page(page =>
{
page.Margin(1.5f, Unit.Centimetre);
page.DefaultTextStyle(x => x.FontFamily("Arial").FontSize(10));
page.Header().Element(ComposeHeader);
page.Content().Element(ComposeContent);
page.Footer().AlignCenter().Text(x => { x.Span("Página "); x.CurrentPageNumber(); });
});
}
void ComposeHeader(IContainer container)
{
container.Column(column =>
{
column.Spacing(5);
column.Item().AlignCenter().Text("Reporte de Consumo de Bobinas por Publicaciones").SemiBold().FontSize(14);
column.Item().PaddingTop(5, Unit.Millimetre).Row(row =>
{
row.RelativeItem().Text(text => { text.Span("Fecha del Reporte: ").SemiBold(); text.Span(Model.FechaReporte); });
row.RelativeItem().AlignRight().Text(text => { text.Span("Periodo: ").SemiBold(); text.Span($"{Model.FechaDesde} - {Model.FechaHasta}"); });
});
});
}
void ComposeContent(IContainer container)
{
container.PaddingTop(1, Unit.Centimetre).Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn(2); // Planta
columns.RelativeColumn(3); // Publicación
columns.RelativeColumn(1.5f); // Kilos
columns.RelativeColumn(1.5f); // Cant. Bobinas
});
table.Header(header =>
{
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).Text("Planta");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).Text("Publicación");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Kilos");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Cant. Bobinas");
});
var gruposPorPlanta = Model.Detalles.GroupBy(p => p.NombrePlanta);
foreach (var grupoPlanta in gruposPorPlanta)
{
var primeraFilaDePlanta = true;
foreach (var detalle in grupoPlanta.OrderBy(d => d.NombrePublicacion))
{
// Celda de Planta (solo en la primera fila del grupo)
if (primeraFilaDePlanta)
{
table.Cell().RowSpan((uint)grupoPlanta.Count()).Border(1).Padding(3).AlignTop().Text(grupoPlanta.Key).SemiBold();
primeraFilaDePlanta = false;
}
table.Cell().Border(1).Padding(3).Text(detalle.NombrePublicacion);
table.Cell().Border(1).Padding(3).AlignRight().Text(detalle.TotalKilos.ToString("N0"));
table.Cell().Border(1).Padding(3).AlignRight().Text(detalle.CantidadBobinas.ToString("N0"));
}
}
// Fila de Totales Generales
var totalGeneralKilos = Model.Detalles.Sum(d => d.TotalKilos);
var totalGeneralBobinas = Model.Detalles.Sum(d => d.CantidadBobinas);
table.Cell().ColumnSpan(2).BorderTop(1.5f).BorderColor(Colors.Black).Padding(3).AlignRight().Text("Totales").ExtraBold();
table.Cell().BorderTop(1.5f).BorderColor(Colors.Black).Padding(3).AlignRight().Text(t => t.Span(totalGeneralKilos.ToString("N0")).ExtraBold());
table.Cell().BorderTop(1.5f).BorderColor(Colors.Black).Padding(3).AlignRight().Text(t => t.Span(totalGeneralBobinas.ToString("N0")).ExtraBold());
});
}
}
}

View File

@@ -0,0 +1,139 @@
using GestionIntegral.Api.Dtos.Reportes;
using GestionIntegral.Api.Dtos.Reportes.ViewModels;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using System.Collections.Generic;
using System.Linq;
namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
{
public class ConsumoBobinasSeccionDocument : IDocument
{
public ConsumoBobinasSeccionViewModel Model { get; }
public ConsumoBobinasSeccionDocument(ConsumoBobinasSeccionViewModel model)
{
Model = model;
}
public DocumentMetadata GetMetadata() => DocumentMetadata.Default;
public void Compose(IDocumentContainer container)
{
container.Page(page =>
{
page.Margin(1.5f, Unit.Centimetre);
page.DefaultTextStyle(x => x.FontFamily("Arial").FontSize(9));
page.Header().Element(ComposeHeader);
page.Content().Element(ComposeContent);
page.Footer().AlignCenter().Text(x => { x.Span("Página "); x.CurrentPageNumber(); });
});
}
void ComposeHeader(IContainer container)
{
container.Column(column =>
{
column.Spacing(5);
column.Item().AlignCenter().Text("Reporte de Consumo de Bobinas por Secciones").SemiBold().FontSize(14);
string subTitle = Model.EsConsolidado ? "Consolidado" : $"Planta: {Model.NombrePlanta}";
column.Item().AlignCenter().Text(subTitle).FontSize(12);
column.Item().PaddingTop(5, Unit.Millimetre).Row(row =>
{
row.RelativeItem().Text(text => { text.Span("Fecha del Reporte: ").SemiBold(); text.Span(Model.FechaReporte); });
row.RelativeItem().AlignRight().Text(text => { text.Span("Periodo: ").SemiBold(); text.Span($"{Model.FechaDesde} - {Model.FechaHasta}"); });
});
// Encabezado de la tabla principal
column.Item().PaddingTop(10).BorderBottom(1.5f).BorderColor(Colors.Black).Padding(4).Row(row =>
{
row.RelativeItem(1.5f).Text("Publicación").SemiBold();
row.RelativeItem(1.5f).Text("Sección").SemiBold();
row.RelativeItem(3).Text("Bobina").SemiBold();
row.RelativeItem(1).AlignRight().Text("Cant. Bobinas").SemiBold();
row.RelativeItem(1).AlignRight().Text("Kilos").SemiBold();
});
});
}
void ComposeContent(IContainer container)
{
container.Column(column =>
{
var gruposPorPublicacion = Model.Detalles.GroupBy(p => p.NombrePublicacion);
foreach (var grupoPub in gruposPorPublicacion)
{
column.Item().Element(c => ComposePublicacionSection(c, grupoPub));
}
// Fila de Totales Generales al final
var totalGeneralBobinas = Model.Detalles.Sum(d => d.CantidadBobinas);
var totalGeneralKilos = Model.Detalles.Sum(d => d.TotalKilos);
column.Item().PaddingTop(10).Row(row =>
{
row.RelativeItem(6).AlignRight().Text("Total General").ExtraBold().FontSize(12);
row.RelativeItem(1).AlignRight().Text(totalGeneralBobinas.ToString("N0")).ExtraBold().FontSize(12);
row.RelativeItem(1).AlignRight().Text(totalGeneralKilos.ToString("N0")).ExtraBold().FontSize(12);
});
});
}
// --- COMPONENTE PARA UNA PUBLICACIÓN ENTERA ---
void ComposePublicacionSection(IContainer container, IGrouping<string, ConsumoBobinasSeccionDto> grupoPub)
{
container.Row(row =>
{
// Columna 1: Nombre de la Publicación
row.RelativeItem(1.5f).BorderBottom(1).BorderLeft(1).BorderRight(1).BorderColor(Colors.Grey.Medium).Padding(3).Text(grupoPub.Key).SemiBold();
// Columna 2: Contiene todas las sub-tablas de secciones
row.RelativeItem(6.5f).Column(seccionesColumn =>
{
var gruposPorSeccion = grupoPub.GroupBy(s => s.NombreSeccion);
foreach(var grupoSec in gruposPorSeccion)
{
seccionesColumn.Item().Element(c => ComposeSeccionTable(c, grupoSec));
}
});
});
}
// --- COMPONENTE PARA LA TABLA DE UNA SECCIÓN ---
void ComposeSeccionTable(IContainer container, IGrouping<string, ConsumoBobinasSeccionDto> grupoSec)
{
container.Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn(1.5f); // Sección
columns.RelativeColumn(3); // Bobina
columns.RelativeColumn(1); // Cantidad
columns.RelativeColumn(1); // Kilos
});
// Filas de detalle
foreach (var detalle in grupoSec.OrderBy(d => d.NombreBobina))
{
table.Cell().Border(1).BorderColor(Colors.Grey.Medium).Padding(3).Text(grupoSec.Key);
table.Cell().Border(1).BorderColor(Colors.Grey.Medium).Padding(3).Text(detalle.NombreBobina);
table.Cell().Border(1).BorderColor(Colors.Grey.Medium).Padding(3).AlignRight().Text(detalle.CantidadBobinas.ToString("N0"));
table.Cell().Border(1).BorderColor(Colors.Grey.Medium).Padding(3).AlignRight().Text(detalle.TotalKilos.ToString("N0"));
}
// Fila de total de la sección
var totalCantSeccion = grupoSec.Sum(d => d.CantidadBobinas);
var totalKilosSeccion = grupoSec.Sum(d => d.TotalKilos);
table.Cell().ColumnSpan(2).Border(1).BorderColor(Colors.Grey.Medium).AlignRight().Padding(3).Text("Total Sección").SemiBold();
table.Cell().Border(1).BorderColor(Colors.Grey.Medium).Padding(3).AlignRight().Text(t => t.Span(totalCantSeccion.ToString("N0")).SemiBold());
table.Cell().Border(1).BorderColor(Colors.Grey.Medium).Padding(3).AlignRight().Text(t => t.Span(totalKilosSeccion.ToString("N0")).SemiBold());
});
}
}
}

View File

@@ -0,0 +1,126 @@
using GestionIntegral.Api.Dtos.Reportes.ViewModels;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using System.Linq;
namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
{
public class ControlDevolucionesDocument : IDocument
{
public ControlDevolucionesViewModel Model { get; }
public ControlDevolucionesDocument(ControlDevolucionesViewModel model)
{
Model = model;
}
public DocumentMetadata GetMetadata() => DocumentMetadata.Default;
public DocumentSettings GetSettings() => DocumentSettings.Default;
public void Compose(IDocumentContainer container)
{
container.Page(page =>
{
page.Margin(1, Unit.Centimetre);
page.DefaultTextStyle(x => x.FontFamily("Roboto").FontSize(11));
page.Header().Element(ComposeHeader);
page.Content().Element(ComposeContent);
});
}
void ComposeHeader(IContainer container)
{
container.Column(column =>
{
column.Item().AlignCenter().Text("Control de Devoluciones").SemiBold().FontSize(16);
column.Item().AlignCenter().Text("Canillas / Accionistas").FontSize(13);
});
}
void ComposeContent(IContainer container)
{
container.PaddingTop(1, Unit.Centimetre).Column(column =>
{
column.Spacing(15);
column.Item().Row(row =>
{
row.RelativeItem().Text(text =>
{
text.Span("Fecha Consultada: ").SemiBold();
text.Span(Model.FechaConsultada);
});
row.RelativeItem().AlignRight().Text(text =>
{
text.Span("Cantidad Canillas: ").SemiBold();
text.Span(Model.CantidadCanillas.ToString());
});
});
column.Item().PaddingTop(5).Border(1).Background(Colors.Grey.Lighten3).AlignCenter().Padding(2).Text(Model.NombreEmpresa).SemiBold();
column.Item().Border(1).Padding(10).Column(innerCol =>
{
innerCol.Spacing(5);
// Fila de "Ingresados por Remito" con borde inferior sólido.
innerCol.Item().BorderBottom(1, Unit.Point).BorderColor(Colors.Grey.Medium).Row(row =>
{
row.RelativeItem().Text("Ingresados por Remito:").SemiBold();
row.RelativeItem().AlignRight().Text(Model.TotalIngresadosPorRemito.ToString("N0"));
}); // <-- SOLUCIÓN: Borde sólido simple.
foreach (var item in Model.Detalles)
{
var totalSeccion = item.Devueltos - item.Llevados;
innerCol.Item().PaddingTop(5).Row(row =>
{
row.ConstantItem(100).Text(item.Tipo).SemiBold();
row.RelativeItem().Column(sub =>
{
sub.Spacing(2);
sub.Item().Row(r => {
r.RelativeItem().Text("Llevados");
r.RelativeItem().AlignRight().Text($"-{item.Llevados:N0}");
});
sub.Item().Row(r => {
r.RelativeItem().Text("Devueltos");
r.RelativeItem().AlignRight().Text($"{item.Devueltos:N0}");
});
sub.Item().BorderTop(1.5f).BorderColor(Colors.Black).PaddingTop(2).Row(r => {
r.RelativeItem().Text(t => t.Span("Total").SemiBold());
r.RelativeItem().AlignRight().Text(t => t.Span(totalSeccion.ToString("N0")).SemiBold());
});
});
});
}
});
column.Item().PaddingTop(10).Column(finalCol =>
{
finalCol.Spacing(2);
Action<RowDescriptor, string, string, bool> AddTotalRow = (row, label, value, isBold) =>
{
var text = row.RelativeItem().Text(label);
if (isBold) text.SemiBold();
var valueText = row.ConstantItem(80).AlignRight().Text(value);
if (isBold) valueText.SemiBold();
};
// Usamos bordes superiores para separar las líneas de total
finalCol.Item().BorderTop(2f).BorderColor(Colors.Black).PaddingTop(2).Row(row => AddTotalRow(row, "Total Devolución a la Fecha", Model.TotalDevolucionALaFecha.ToString("N0"), false));
finalCol.Item().BorderTop(1).BorderColor(Colors.Grey.Lighten2).PaddingTop(2).Row(row => AddTotalRow(row, "Total Devolución Días Anteriores", Model.TotalDevolucionDiasAnteriores.ToString("N0"), false));
finalCol.Item().BorderTop(1).BorderColor(Colors.Grey.Lighten2).PaddingTop(2).Row(row => AddTotalRow(row, "Total Devolución", Model.TotalDevolucionGeneral.ToString("N0"), false));
finalCol.Item().BorderTop(2f).BorderColor(Colors.Black).PaddingTop(5).Row(row => AddTotalRow(row, "Sin Cargo", Model.TotalSinCargo.ToString("N0"), false));
finalCol.Item().BorderTop(1).BorderColor(Colors.Grey.Lighten2).PaddingTop(2).Row(row => AddTotalRow(row, "Sobrantes", $"-{Model.TotalSobrantes.ToString("N0")}", false));
finalCol.Item().BorderTop(1).BorderColor(Colors.Grey.Lighten2).BorderBottom(1).BorderColor(Colors.Grey.Lighten2).PaddingTop(5).Row(row => AddTotalRow(row, "Diferencia", Model.DiferenciaFinal.ToString("N0"), true));
});
});
}
}
}

View File

@@ -0,0 +1,260 @@
using GestionIntegral.Api.Dtos.Reportes.ViewModels;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using System.Globalization;
using System.Linq;
namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
{
public class CuentasDistribuidorDocument : IDocument
{
public CuentasDistribuidorViewModel Model { get; }
private static readonly CultureInfo CultureAr = new CultureInfo("es-AR", false)
{
NumberFormat = { CurrencySymbol = "$" }
};
public CuentasDistribuidorDocument(CuentasDistribuidorViewModel model)
{
Model = model;
}
public DocumentMetadata GetMetadata() => DocumentMetadata.Default;
public DocumentSettings GetSettings() => DocumentSettings.Default;
public void Compose(IDocumentContainer container)
{
container.Page(page =>
{
page.Margin(1, Unit.Centimetre);
page.DefaultTextStyle(x => x.FontFamily("Arial").FontSize(9));
page.Header().Element(ComposeHeader);
page.Content().Element(ComposeContent);
page.Footer().AlignCenter().Text(x => { x.Span("Página "); x.CurrentPageNumber(); x.Span(" de "); x.TotalPages(); });
});
}
void ComposeHeader(IContainer container)
{
container.Column(column =>
{
column.Item().Row(row =>
{
row.RelativeItem().Text($"Fecha de Reporte {Model.FechaReporte}");
row.RelativeItem().AlignCenter().Text("Cuenta De Distribuidor").SemiBold().FontSize(14);
row.RelativeItem();
});
column.Item().AlignCenter().Text(Model.NombreDistribuidor).FontSize(12);
column.Item().PaddingTop(2, Unit.Millimetre).Row(row =>
{
row.RelativeItem(2);
row.RelativeItem(8).AlignCenter().Text(text =>
{
text.Span("Fecha Consultada: Desde ").SemiBold();
text.Span(Model.FechaDesde);
text.Span(" Hasta ").SemiBold();
text.Span(Model.FechaHasta);
});
row.RelativeItem(2);
});
});
}
void ComposeContent(IContainer container)
{
container.PaddingTop(1, Unit.Centimetre).Column(column =>
{
column.Spacing(20);
if (Model.Movimientos.Any()) column.Item().Element(ComposeMovimientosTable);
if (Model.Pagos.Any()) column.Item().Element(ComposePagosTable);
if (Model.DebitosCreditos.Any()) column.Item().Element(ComposeDebCredTable);
column.Item().Element(ComposeResumenPeriodo);
column.Item().Element(ComposeSaldoFinal);
});
}
void ComposeMovimientosTable(IContainer container)
{
container.Column(column =>
{
column.Item().PaddingBottom(5).Text("Movimientos").SemiBold().FontSize(11);
column.Item().Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.ConstantColumn(60);
columns.RelativeColumn(2);
columns.ConstantColumn(50);
columns.ConstantColumn(50);
columns.RelativeColumn(1.5f);
columns.RelativeColumn(1.5f);
columns.RelativeColumn(2);
});
table.Header(header =>
{
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).Text("Fecha");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).Text("Publicacion");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).Text("Remito");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignCenter().Text("Cantidad");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Debe");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Haber");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Saldo");
});
decimal saldoAcumulado = 0; // Inicia en CERO
foreach (var item in Model.Movimientos.OrderBy(m => m.Fecha))
{
saldoAcumulado += (item.Debe - item.Haber);
table.Cell().Border(1).Padding(2).Text(item.Fecha.ToString("dd/MM/yyyy"));
table.Cell().Border(1).Padding(2).Text(item.Publicacion);
table.Cell().Border(1).Padding(2).Text(item.Remito);
table.Cell().Border(1).Padding(2).AlignCenter().Text(item.Cantidad.ToString("N0"));
table.Cell().Border(1).Padding(2).AlignRight().Text(item.Debe.ToString("C", CultureAr));
table.Cell().Border(1).Padding(2).AlignRight().Text(item.Haber.ToString("C", CultureAr));
table.Cell().Border(1).Padding(2).AlignRight().Text(saldoAcumulado.ToString("C", CultureAr));
}
table.Cell().ColumnSpan(4).Border(1).AlignRight().Padding(2).Text(t => t.Span("Totales").SemiBold());
table.Cell().Border(1).AlignRight().Padding(2).Text(t => t.Span(Model.Movimientos.Sum(x => x.Debe).ToString("C", CultureAr)).SemiBold());
table.Cell().Border(1).AlignRight().Padding(2).Text(t => t.Span(Model.Movimientos.Sum(x => x.Haber).ToString("C", CultureAr)).SemiBold());
table.Cell().Border(1).AlignRight().Padding(2).Text(t => t.Span(saldoAcumulado.ToString("C", CultureAr)).SemiBold());
});
});
}
void ComposePagosTable(IContainer container)
{
container.Column(column =>
{
column.Item().PaddingBottom(5).Text("Pagos").SemiBold().FontSize(12);
column.Item().Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.ConstantColumn(60);
columns.RelativeColumn(1.5f);
columns.ConstantColumn(50);
columns.RelativeColumn(1.5f);
columns.RelativeColumn(1.5f);
columns.RelativeColumn(1.5f);
columns.RelativeColumn(2);
});
table.Header(header =>
{
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).Text("Fecha");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).Text("Recibo");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).Text("Tipo");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Debe");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Haber");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Saldo");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).Text("Detalle");
});
decimal saldoAcumulado = Model.TotalMovimientos;
foreach (var item in Model.Pagos.OrderBy(p => p.Fecha).ThenBy(p => p.Recibo))
{
saldoAcumulado += (item.Debe - item.Haber);
table.Cell().Border(1).Padding(2).Text(item.Fecha.ToString("dd/MM/yyyy"));
table.Cell().Border(1).Padding(2).Text(item.Recibo.ToString());
table.Cell().Border(1).Padding(2).Text(item.Tipo);
table.Cell().Border(1).Padding(2).AlignRight().Text(item.Debe.ToString("C", CultureAr));
table.Cell().Border(1).Padding(2).AlignRight().Text(item.Haber.ToString("C", CultureAr));
table.Cell().Border(1).Padding(2).AlignRight().Text(saldoAcumulado.ToString("C", CultureAr));
table.Cell().Border(1).Padding(2).Text(item.Detalle);
}
table.Cell().ColumnSpan(3).Border(1).AlignRight().Padding(2).Text(t => t.Span("Totales").SemiBold());
table.Cell().Border(1).AlignRight().Padding(2).Text(t => t.Span(Model.Pagos.Sum(x => x.Debe).ToString("C", CultureAr)).SemiBold());
table.Cell().Border(1).AlignRight().Padding(2).Text(t => t.Span(Model.Pagos.Sum(x => x.Haber).ToString("C", CultureAr)).SemiBold());
table.Cell().Border(1).AlignRight().Padding(2).Text(t => t.Span(saldoAcumulado.ToString("C", CultureAr)).SemiBold());
table.Cell().Border(1);
});
});
}
void ComposeDebCredTable(IContainer container)
{
container.Column(column =>
{
column.Item().PaddingBottom(5).Text("Débitos / Créditos").SemiBold().FontSize(12);
column.Item().Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.ConstantColumn(65);
columns.RelativeColumn(2);
columns.RelativeColumn(1);
columns.RelativeColumn(1);
columns.RelativeColumn(2);
});
table.Header(header =>
{
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).Text("Fecha");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).Text("Referencia");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Debe");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Haber");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Saldo");
});
decimal saldoAcumulado = Model.TotalMovimientos + Model.TotalPagos;
foreach (var item in Model.DebitosCreditos.OrderBy(dc => dc.Fecha))
{
saldoAcumulado += (item.Debe - item.Haber);
table.Cell().Border(1).Padding(2).Text(item.Fecha.ToString("dd/MM/yyyy"));
table.Cell().Border(1).Padding(2).Text(item.Referencia);
table.Cell().Border(1).Padding(2).AlignRight().Text(item.Debe.ToString("C", CultureAr));
table.Cell().Border(1).Padding(2).AlignRight().Text(item.Haber.ToString("C", CultureAr));
table.Cell().Border(1).Padding(2).AlignRight().Text(saldoAcumulado.ToString("C", CultureAr));
}
table.Cell().ColumnSpan(2).Border(1).AlignRight().Padding(2).Text(t => t.Span("Totales").SemiBold());
table.Cell().Border(1).AlignRight().Padding(2).Text(t => t.Span(Model.DebitosCreditos.Sum(x => x.Debe).ToString("C", CultureAr)).SemiBold());
table.Cell().Border(1).AlignRight().Padding(2).Text(t => t.Span(Model.DebitosCreditos.Sum(x => x.Haber).ToString("C", CultureAr)).SemiBold());
table.Cell().Border(1).AlignRight().Padding(2).Text(t => t.Span(saldoAcumulado.ToString("C", CultureAr)).SemiBold());
});
});
}
void ComposeResumenPeriodo(IContainer container)
{
container.PaddingTop(5, Unit.Millimetre).AlignLeft().Column(column =>
{
column.Item().Border(1).Padding(5).Width(300, Unit.Point).Column(col =>
{
col.Spacing(5);
col.Item().Text("Datos totales del periodo consultado").SemiBold();
Action<RowDescriptor, string, decimal> AddResumenRow = (row, label, value) =>
{
row.RelativeItem().Text(label);
row.ConstantItem(120).AlignRight().Text(value.ToString("C", CultureAr));
};
col.Item().Row(row => AddResumenRow(row, "Movimientos", Model.TotalMovimientos));
col.Item().Row(row => AddResumenRow(row, "Débitos/Créditos", Model.TotalDebitosCreditos));
col.Item().Row(row => AddResumenRow(row, "Pagos", Model.TotalPagos));
col.Item().BorderTop(1.5f).BorderColor(Colors.Black).PaddingTop(5).Row(row =>
{
row.RelativeItem().Text("Total").SemiBold();
row.ConstantItem(120).AlignRight().Text(t => t.Span(Model.TotalPeriodo.ToString("C", CultureAr)).SemiBold());
});
});
});
}
void ComposeSaldoFinal(IContainer container)
{
container.PaddingTop(5, Unit.Millimetre).AlignLeft().Text(text =>
{
text.Span($"Saldo Total del Distribuidor al {Model.FechaReporte} ").SemiBold().FontSize(12);
text.Span(Model.SaldoDeCuenta.ToString("C", CultureAr)).SemiBold().FontSize(12);
});
}
}
}

View File

@@ -0,0 +1,240 @@
using GestionIntegral.Api.Dtos.Reportes;
using GestionIntegral.Api.Dtos.Reportes.ViewModels;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using System.Globalization;
namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
{
public class DistribucionCanillasDocument : IDocument
{
public DistribucionCanillasViewModel Model { get; }
private static readonly CultureInfo CultureAr = new CultureInfo("es-AR");
public DistribucionCanillasDocument(DistribucionCanillasViewModel model)
{
Model = model;
}
public DocumentMetadata GetMetadata() => DocumentMetadata.Default;
public void Compose(IDocumentContainer container)
{
container.Page(page =>
{
page.Margin(1, Unit.Centimetre);
page.DefaultTextStyle(x => x.FontFamily("Arial").FontSize(9));
page.Header().Element(ComposeHeader);
page.Content().Element(ComposeContent);
page.Footer().AlignCenter().Text(x => { x.Span("Página "); x.CurrentPageNumber(); });
});
}
void ComposeHeader(IContainer container)
{
container.Column(column =>
{
column.Item().Row(row => {
row.RelativeItem().Text("Listado de Distribución: Canillas / Accionistas").FontSize(12);
row.RelativeItem().AlignRight().Text(text => {
text.Span("Fecha Consultada ").SemiBold().FontSize(12);
text.Span(Model.FechaConsultada).FontSize(12);
});
});
column.Item().PaddingTop(5).Row(row => {
row.RelativeItem().Text(text => {
text.Span("Fecha de Generación del Reporte ").SemiBold().FontSize(12);
text.Span(Model.FechaReporte);
});
row.RelativeItem().AlignRight().Text(text => {
text.Span("Empresa ").SemiBold().FontSize(12);
text.Span(Model.Empresa).FontSize(12);
});
});
});
}
void ComposeContent(IContainer container)
{
container.PaddingTop(5, Unit.Millimetre).Column(column =>
{
column.Spacing(15);
if(Model.Canillas.Any())
column.Item().Element(c => ComposeTablaDetallada(c, "Canillas", Model.Canillas));
if(Model.CanillasAccionistas.Any())
column.Item().Element(c => ComposeTablaDetallada(c, "Accionistas", Model.CanillasAccionistas));
if(Model.CanillasLiquidadasOtraFecha.Any())
column.Item().Element(c => ComposeTablaLiquidacion(c, "Liquidación de movimientos de otras fechas canillas", Model.CanillasLiquidadasOtraFecha));
if(Model.CanillasAccionistasLiquidadasOtraFecha.Any())
column.Item().Element(c => ComposeTablaLiquidacion(c, "Liquidación de movimientos de otras fechas accionistas", Model.CanillasAccionistasLiquidadasOtraFecha));
if(Model.CanillasTodos.Any())
column.Item().Element(c => ComposeTablaTotales(c, "Recuento de Publicaciones", Model.CanillasTodos));
if(Model.RemitoIngresado > 0)
column.Item().Element(ComposeResumenDevoluciones);
});
}
void ComposeTablaDetallada(IContainer container, string title, IEnumerable<DetalleDistribucionCanillaDto> data)
{
container.Column(column =>
{
column.Item().PaddingBottom(4).Text(title).SemiBold().FontSize(11);
column.Item().Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn(3); columns.RelativeColumn(3);
columns.RelativeColumn(1); columns.RelativeColumn(1);
columns.RelativeColumn(1); columns.RelativeColumn(1.2f);
});
table.Header(header =>
{
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).Text("Vendedor");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).Text("Publicación");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Llevados");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Devueltos");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Vendidos");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("A Rendir");
});
foreach (var grupoCanilla in data.GroupBy(c => c.Canilla))
{
var primeraFila = true;
// El RowSpan es el número de publicaciones + la fila de total
var totalFilasGrupo = grupoCanilla.Count() + 1;
foreach (var item in grupoCanilla.OrderBy(p => p.Publicacion))
{
if (primeraFila)
{
table.Cell().RowSpan((uint)totalFilasGrupo).Border(1).Padding(2).AlignTop().Text(item.Canilla);
primeraFila = false;
}
table.Cell().Border(1).Padding(2).Text(item.Publicacion);
table.Cell().Border(1).Padding(2).AlignRight().Text(item.TotalCantSalida.ToString("N0"));
table.Cell().Border(1).Padding(2).AlignRight().Text(item.TotalCantEntrada.ToString("N0"));
table.Cell().Border(1).Padding(2).AlignRight().Text((item.TotalCantSalida - item.TotalCantEntrada).ToString("N0"));
table.Cell().Border(1).Padding(2).AlignRight().Text(item.TotalRendir.ToString("C", CultureAr));
}
// Subtotal por canilla
table.Cell().Border(1).Padding(2).AlignRight().Text("Totales").SemiBold();
table.Cell().Border(1).Padding(2).AlignRight().Text(t => t.Span(grupoCanilla.Sum(i => i.TotalCantSalida).ToString("N0")).SemiBold());
table.Cell().Border(1).Padding(2).AlignRight().Text(t => t.Span(grupoCanilla.Sum(i => i.TotalCantEntrada).ToString("N0")).SemiBold());
table.Cell().Border(1).Padding(2).AlignRight().Text(t => t.Span(grupoCanilla.Sum(i => i.TotalCantSalida - i.TotalCantEntrada).ToString("N0")).SemiBold());
table.Cell().Border(1).Padding(2).AlignRight().Text(t => t.Span(grupoCanilla.Sum(i => i.TotalRendir).ToString("C", CultureAr)).SemiBold());
}
var boldStyle = TextStyle.Default.ExtraBold();
table.Cell().ColumnSpan(2).BorderTop(2).BorderColor(Colors.Black).Padding(2).AlignRight().Text(t => t.Span("Total " + title).Style(boldStyle));
table.Cell().BorderTop(2).BorderColor(Colors.Black).Padding(2).AlignRight().Text(t => t.Span(data.Sum(i => i.TotalCantSalida).ToString("N0")).Style(boldStyle));
table.Cell().BorderTop(2).BorderColor(Colors.Black).Padding(2).AlignRight().Text(t => t.Span(data.Sum(i => i.TotalCantEntrada).ToString("N0")).Style(boldStyle));
table.Cell().BorderTop(2).BorderColor(Colors.Black).Padding(2).AlignRight().Text(t => t.Span(data.Sum(i => i.TotalCantSalida - i.TotalCantEntrada).ToString("N0")).Style(boldStyle));
table.Cell().BorderTop(2).BorderColor(Colors.Black).Padding(2).AlignRight().Text(t => t.Span(data.Sum(i => i.TotalRendir).ToString("C", CultureAr)).Style(boldStyle));
});
});
}
void ComposeTablaLiquidacion(IContainer container, string title, IEnumerable<DetalleDistribucionCanillaDto> data)
{
container.Column(column =>
{
column.Item().PaddingBottom(4).Text(title).SemiBold().FontSize(11);
column.Item().Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn(3); columns.RelativeColumn(1.5f);
columns.RelativeColumn(3); columns.RelativeColumn(1);
columns.RelativeColumn(1); columns.RelativeColumn(1);
columns.RelativeColumn(1.2f);
});
table.Header(header =>
{
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).Text("Vendedor");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).Text("Fecha");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).Text("Publicación");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Llevados");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Devueltos");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Vendidos");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("A Rendir");
});
foreach(var item in data.OrderBy(d => d.Canilla).ThenBy(d => d.Fecha))
{
table.Cell().Border(1).Padding(2).Text(item.Canilla);
table.Cell().Border(1).Padding(2).Text(item.Fecha?.ToString("dd/MM/yyyy") ?? "");
table.Cell().Border(1).Padding(2).Text(item.Publicacion);
table.Cell().Border(1).Padding(2).AlignRight().Text(item.TotalCantSalida.ToString("N0"));
table.Cell().Border(1).Padding(2).AlignRight().Text(item.TotalCantEntrada.ToString("N0"));
table.Cell().Border(1).Padding(2).AlignRight().Text((item.TotalCantSalida - item.TotalCantEntrada).ToString("N0"));
table.Cell().Border(1).Padding(2).AlignRight().Text(item.TotalRendir.ToString("C", CultureAr));
}
var boldStyle = TextStyle.Default.ExtraBold();
table.Cell().ColumnSpan(3).BorderTop(2).BorderColor(Colors.Black).Padding(2).AlignRight().Text(t => t.Span("Total").Style(boldStyle));
table.Cell().BorderTop(2).BorderColor(Colors.Black).Padding(2).AlignRight().Text(t => t.Span(data.Sum(i => i.TotalCantSalida).ToString("N0")).Style(boldStyle));
table.Cell().BorderTop(2).BorderColor(Colors.Black).Padding(2).AlignRight().Text(t => t.Span(data.Sum(i => i.TotalCantEntrada).ToString("N0")).Style(boldStyle));
table.Cell().BorderTop(2).BorderColor(Colors.Black).Padding(2).AlignRight().Text(t => t.Span(data.Sum(i => i.TotalCantSalida - i.TotalCantEntrada).ToString("N0")).Style(boldStyle));
table.Cell().BorderTop(2).BorderColor(Colors.Black).Padding(2).AlignRight().Text(t => t.Span(data.Sum(i => i.TotalRendir).ToString("C", CultureAr)).Style(boldStyle));
});
});
}
void ComposeTablaTotales(IContainer container, string title, IEnumerable<DetalleDistribucionCanillaAllDto> data)
{
container.Column(column =>
{
column.Item().PaddingBottom(4).Text(title).SemiBold().FontSize(11);
column.Item().Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn(2); columns.RelativeColumn(3);
columns.RelativeColumn(1.2f); columns.RelativeColumn(1.2f);
columns.RelativeColumn(1.2f); columns.RelativeColumn(1.5f);
});
table.Header(header =>
{
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).Text("Tipo Vendedor");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).Text("Publicación");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Llevados");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Devueltos");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Vendidos");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("A Rendir");
});
foreach(var item in data.OrderBy(d => d.TipoVendedor).ThenBy(d => d.Publicacion))
{
table.Cell().Border(1).Padding(2).Text(item.TipoVendedor);
table.Cell().Border(1).Padding(2).Text(item.Publicacion);
table.Cell().Border(1).Padding(2).AlignRight().Text(item.TotalCantSalida.ToString("N0"));
table.Cell().Border(1).Padding(2).AlignRight().Text(item.TotalCantEntrada.ToString("N0"));
table.Cell().Border(1).Padding(2).AlignRight().Text((item.TotalCantSalida-item.TotalCantEntrada).ToString("N0"));
table.Cell().Border(1).Padding(2).AlignRight().Text(item.TotalRendir.ToString("C", CultureAr));
}
var boldStyle = TextStyle.Default.ExtraBold();
table.Cell().ColumnSpan(2).BorderTop(2).BorderColor(Colors.Black).Padding(2).AlignRight().Text(t => t.Span("Total General").Style(boldStyle));
table.Cell().BorderTop(2).BorderColor(Colors.Black).Padding(2).AlignRight().Text(t => t.Span(data.Sum(i => i.TotalCantSalida).ToString("N0")).Style(boldStyle));
table.Cell().BorderTop(2).BorderColor(Colors.Black).Padding(2).AlignRight().Text(t => t.Span(data.Sum(i => i.TotalCantEntrada).ToString("N0")).Style(boldStyle));
table.Cell().BorderTop(2).BorderColor(Colors.Black).Padding(2).AlignRight().Text(t => t.Span(data.Sum(i => i.TotalCantSalida - i.TotalCantEntrada).ToString("N0")).Style(boldStyle));
table.Cell().BorderTop(2).BorderColor(Colors.Black).Padding(2).AlignRight().Text(t => t.Span(data.Sum(i => i.TotalRendir).ToString("C", CultureAr)).Style(boldStyle));
});
});
}
void ComposeResumenDevoluciones(IContainer container)
{
container.PaddingTop(5).Column(column => {
column.Item().Row(row => {
row.Spacing(15);
row.AutoItem().Text(text => { text.Span("Remito: ").SemiBold().FontSize(11); text.Span(Model.RemitoIngresado.ToString("N0")).FontSize(11); });
row.AutoItem().Text(text => { text.Span("Devolución: ").SemiBold().FontSize(11); text.Span(Model.DevolucionTotal.ToString("N0")).FontSize(11); });
row.AutoItem().Text(text => { text.Span("Venta: ").SemiBold().FontSize(11); text.Span(Model.VentaTotal.ToString("N0")).FontSize(11); });
});
});
}
}
}

View File

@@ -0,0 +1,126 @@
using GestionIntegral.Api.Dtos.Reportes.ViewModels;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using System.Globalization;
using System.Linq;
namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
{
public class DistribucionCanillasTotalesDocument : IDocument
{
public DistribucionCanillasViewModel Model { get; }
private static readonly CultureInfo CultureAr = new CultureInfo("es-AR");
public DistribucionCanillasTotalesDocument(DistribucionCanillasViewModel model)
{
Model = model;
}
public DocumentMetadata GetMetadata() => DocumentMetadata.Default;
public void Compose(IDocumentContainer container)
{
container.Page(page =>
{
page.Margin(1, Unit.Centimetre);
page.DefaultTextStyle(x => x.FontFamily("Arial").FontSize(10));
page.Header().Element(ComposeHeader);
page.Content().Element(ComposeContent);
page.Footer().AlignCenter().Text(x => { x.Span("Página "); x.CurrentPageNumber(); });
});
}
void ComposeHeader(IContainer container)
{
container.Column(column =>
{
column.Item().Row(row => {
row.RelativeItem().Text("Listado de Distribución: Canillas / Accionistas (Totales)").FontSize(12);
row.RelativeItem().AlignRight().Text(text => {
text.Span("Fecha Consultada ").SemiBold().FontSize(12);
text.Span(Model.FechaConsultada).FontSize(12);
});
});
column.Item().PaddingTop(5).Row(row => {
row.RelativeItem().Text(text => {
text.Span("Fecha de Generación del Reporte ").SemiBold().FontSize(12);
text.Span(Model.FechaReporte).FontSize(12);
});
row.RelativeItem().AlignRight().Text(text => {
text.Span("Empresa ").SemiBold().FontSize(12);
text.Span(Model.Empresa).FontSize(12);
});
});
});
}
void ComposeContent(IContainer container)
{
container.PaddingTop(5, Unit.Millimetre).Column(column =>
{
column.Spacing(15);
if(Model.CanillasTodos.Any())
column.Item().Element(ComposeTablaTotales);
if(Model.RemitoIngresado > 0)
column.Item().Element(ComposeResumenDevoluciones);
});
}
void ComposeTablaTotales(IContainer container)
{
container.Column(column =>
{
column.Item().PaddingBottom(4).Text("Recuento de Publicaciones").SemiBold().FontSize(11);
column.Item().Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn(2); columns.RelativeColumn(3);
columns.RelativeColumn(1.2f); columns.RelativeColumn(1.2f);
columns.RelativeColumn(1.2f); columns.RelativeColumn(1.5f);
});
table.Header(header =>
{
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).Text("Tipo Vendedor");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).Text("Publicación");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Llevados");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Devueltos");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Vendidos");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("A Rendir");
});
foreach(var item in Model.CanillasTodos.OrderBy(d => d.TipoVendedor).ThenBy(d => d.Publicacion))
{
table.Cell().Border(1).Padding(2).Text(item.TipoVendedor);
table.Cell().Border(1).Padding(2).Text(item.Publicacion);
table.Cell().Border(1).Padding(2).AlignRight().Text(item.TotalCantSalida.ToString("N0"));
table.Cell().Border(1).Padding(2).AlignRight().Text(item.TotalCantEntrada.ToString("N0"));
table.Cell().Border(1).Padding(2).AlignRight().Text((item.TotalCantSalida-item.TotalCantEntrada).ToString("N0"));
table.Cell().Border(1).Padding(2).AlignRight().Text(item.TotalRendir.ToString("C", CultureAr));
}
var boldStyle = TextStyle.Default.ExtraBold();
table.Cell().ColumnSpan(2).BorderTop(2).BorderColor(Colors.Black).Padding(2).AlignRight().Text(t => t.Span("Total General").Style(boldStyle));
table.Cell().BorderTop(2).BorderColor(Colors.Black).Padding(2).AlignRight().Text(t => t.Span(Model.CanillasTodos.Sum(i => i.TotalCantSalida).ToString("N0")).Style(boldStyle));
table.Cell().BorderTop(2).BorderColor(Colors.Black).Padding(2).AlignRight().Text(t => t.Span(Model.CanillasTodos.Sum(i => i.TotalCantEntrada).ToString("N0")).Style(boldStyle));
table.Cell().BorderTop(2).BorderColor(Colors.Black).Padding(2).AlignRight().Text(t => t.Span(Model.CanillasTodos.Sum(i => i.TotalCantSalida - i.TotalCantEntrada).ToString("N0")).Style(boldStyle));
table.Cell().BorderTop(2).BorderColor(Colors.Black).Padding(2).AlignRight().Text(t => t.Span(Model.CanillasTodos.Sum(i => i.TotalRendir).ToString("C", CultureAr)).Style(boldStyle));
});
});
}
void ComposeResumenDevoluciones(IContainer container)
{
container.PaddingTop(10).Column(column => {
column.Item().Row(row => {
row.Spacing(15);
row.AutoItem().Text(text => { text.Span("Remito: ").SemiBold(); text.Span(Model.RemitoIngresado.ToString("N0")); });
row.AutoItem().Text(text => { text.Span("Devolución: ").SemiBold(); text.Span(Model.DevolucionTotal.ToString("N0")); });
row.AutoItem().Text(text => { text.Span("Venta: ").SemiBold(); text.Span(Model.VentaTotal.ToString("N0")); });
});
});
}
}
}

View File

@@ -0,0 +1,152 @@
using GestionIntegral.Api.Dtos.Reportes.ViewModels;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
{
public class DistribucionSuscripcionesDocument : IDocument
{
public DistribucionSuscripcionesViewModel Model { get; }
public DistribucionSuscripcionesDocument(DistribucionSuscripcionesViewModel model)
{
Model = model;
}
public DocumentMetadata GetMetadata() => DocumentMetadata.Default;
public void Compose(IDocumentContainer container)
{
container.Page(page =>
{
page.Margin(1, Unit.Centimetre);
page.DefaultTextStyle(x => x.FontFamily("Arial").FontSize(9));
page.Header().Element(ComposeHeader);
page.Content().Element(ComposeContent);
page.Footer().AlignCenter().Text(x => { x.Span("Página "); x.CurrentPageNumber(); });
});
}
void ComposeHeader(IContainer container)
{
container.Column(column =>
{
column.Item().Row(row =>
{
row.RelativeItem().Column(col =>
{
col.Item().Text("Reporte de Distribución de Suscripciones").SemiBold().FontSize(14);
col.Item().Text($"Período: {Model.FechaDesde} al {Model.FechaHasta}").FontSize(11);
});
row.ConstantItem(150).AlignRight().Text($"Generado: {Model.FechaGeneracion}");
});
column.Item().PaddingTop(5).BorderBottom(1).BorderColor(Colors.Grey.Lighten2);
});
}
void ComposeContent(IContainer container)
{
container.PaddingTop(10).Column(column =>
{
column.Spacing(20); // Espacio entre elementos principales (sección de altas y sección de bajas)
// --- Sección 1: Altas y Activas ---
column.Item().Column(colAltas =>
{
colAltas.Item().Text("Altas y Suscripciones Activas en el Período").Bold().FontSize(14).Underline();
colAltas.Item().PaddingBottom(10).Text("Listado de suscriptores que deben recibir entregas en el período seleccionado.");
if (!Model.DatosAgrupadosAltas.Any())
{
colAltas.Item().PaddingTop(10).Text("No se encontraron suscripciones activas para este período.").Italic();
}
else
{
foreach (var empresa in Model.DatosAgrupadosAltas)
{
colAltas.Item().Element(c => ComposeTablaEmpresa(c, empresa, esBaja: false));
}
}
});
// --- Sección 2: Bajas ---
if (Model.DatosAgrupadosBajas.Any())
{
column.Item().PageBreak(); // Salto de página para separar las secciones
column.Item().Column(colBajas =>
{
colBajas.Item().Text("Bajas de Suscripciones en el Período").Bold().FontSize(14).Underline().FontColor(Colors.Red.Medium);
colBajas.Item().PaddingBottom(10).Text("Listado de suscriptores cuya suscripción finalizó. NO se les debe entregar a partir de su 'Fecha de Baja'.");
foreach (var empresa in Model.DatosAgrupadosBajas)
{
colBajas.Item().Element(c => ComposeTablaEmpresa(c, empresa, esBaja: true));
}
});
}
});
}
void ComposeTablaEmpresa(IContainer container, GrupoEmpresa empresa, bool esBaja)
{
container.Column(column =>
{
// Cabecera de la EMPRESA (ej. EL DIA)
column.Item().Background(Colors.Grey.Lighten2).Padding(5).Text(empresa.NombreEmpresa).Bold().FontSize(12);
// Contenedor para las tablas de las publicaciones de esta empresa
column.Item().PaddingTop(5).Column(colPub =>
{
colPub.Spacing(10); // Espacio entre cada tabla de publicación
foreach (var publicacion in empresa.Publicaciones)
{
colPub.Item().Element(c => ComposeTablaPublicacion(c, publicacion, esBaja));
}
});
});
}
void ComposeTablaPublicacion(IContainer container, GrupoPublicacion publicacion, bool esBaja)
{
// Se envuelve la tabla en una columna para poder ponerle un título simple arriba.
container.Column(column =>
{
column.Item().PaddingLeft(2).PaddingBottom(2).Text(publicacion.NombrePublicacion).SemiBold().FontSize(10);
column.Item().Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn(2.5f); // Nombre
columns.RelativeColumn(3); // Dirección
columns.RelativeColumn(1.5f); // Teléfono
columns.ConstantColumn(65); // Fecha Inicio / Baja
columns.RelativeColumn(1.5f); // Días
columns.RelativeColumn(2.5f); // Observaciones
});
table.Header(header =>
{
header.Cell().BorderBottom(1).Padding(2).Text("Suscriptor").SemiBold();
header.Cell().BorderBottom(1).Padding(2).Text("Dirección").SemiBold();
header.Cell().BorderBottom(1).Padding(2).Text("Teléfono").SemiBold();
header.Cell().BorderBottom(1).Padding(2).Text(esBaja ? "Fecha de Baja" : "Fecha Inicio").SemiBold();
header.Cell().BorderBottom(1).Padding(2).Text("Días Entrega").SemiBold();
header.Cell().BorderBottom(1).Padding(2).Text("Observaciones").SemiBold();
});
foreach (var item in publicacion.Suscripciones)
{
table.Cell().Padding(2).Text(item.NombreSuscriptor);
table.Cell().Padding(2).Text(item.Direccion);
table.Cell().Padding(2).Text(item.Telefono ?? "-");
var fecha = esBaja ? item.FechaFin : item.FechaInicio;
table.Cell().Padding(2).Text(fecha?.ToString("dd/MM/yyyy") ?? "-");
table.Cell().Padding(2).Text(item.DiasEntrega);
table.Cell().Padding(2).Text(item.Observaciones ?? "-");
}
});
});
}
}
}

View File

@@ -0,0 +1,114 @@
using GestionIntegral.Api.Dtos.Reportes.ViewModels;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using System.Linq;
namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
{
public class ExistenciaPapelConsolidadoDocument : IDocument
{
public ExistenciaPapelConsolidadoViewModel Model { get; }
public ExistenciaPapelConsolidadoDocument(ExistenciaPapelConsolidadoViewModel model)
{
Model = model;
}
public DocumentMetadata GetMetadata() => DocumentMetadata.Default;
public DocumentSettings GetSettings() => DocumentSettings.Default;
public void Compose(IDocumentContainer container)
{
container
.Page(page =>
{
page.Margin(1, Unit.Centimetre);
page.PageColor(Colors.White);
page.DefaultTextStyle(x => x.FontFamily("Roboto").FontSize(10));
page.Header().Element(ComposeHeader);
page.Content().Element(ComposeContent);
page.Footer()
.AlignCenter()
.Text(x =>
{
x.Span("Página ");
x.CurrentPageNumber();
});
});
}
void ComposeHeader(IContainer container)
{
container.Column(column =>
{
column.Spacing(5);
column.Item().AlignCenter().Text("Reporte de Existencias de Papel").SemiBold().FontSize(14);
// La versión consolidada no muestra la planta, sino el texto "Consolidado"
column.Item().AlignCenter().Text("Consolidado").FontSize(12);
column.Item().PaddingTop(2, Unit.Millimetre).Row(row =>
{
row.RelativeItem().Column(col =>
{
col.Item().Text(text =>
{
text.Span("Fecha del Reporte: ").SemiBold();
text.Span(Model.FechaReporte);
});
col.Item().Text($"Periodo Consultado: Desde {Model.FechaDesde} Hasta {Model.FechaHasta}");
});
});
});
}
void ComposeContent(IContainer container)
{
container.PaddingTop(1, Unit.Centimetre).Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn();
columns.ConstantColumn(65);
columns.ConstantColumn(70);
columns.ConstantColumn(85);
columns.ConstantColumn(65);
columns.ConstantColumn(80);
});
table.Header(header =>
{
header.Cell().Background(Colors.Grey.Lighten3).Padding(4).Text("Tipo de Bobina");
header.Cell().Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Cant. Stock");
header.Cell().Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Kg. Stock");
header.Cell().Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Consumo (Kg)");
header.Cell().Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Días Disp.");
header.Cell().Background(Colors.Grey.Lighten3).Padding(4).AlignCenter().Text("Fin Estimado");
});
foreach (var item in Model.Existencias)
{
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(4).Text(item.TipoBobina);
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(4).AlignRight().Text(item.BobinasEnStock?.ToString("N0") ?? "0");
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(4).AlignRight().Text(item.TotalKilosEnStock?.ToString("N0") ?? "0");
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(4).AlignRight().Text(item.ConsumoAcumulado?.ToString("N0") ?? "0");
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(4).AlignRight().Text(item.PromedioDiasDisponibles?.ToString("N0") ?? "N/A");
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(4).AlignCenter().Text(item.FechaEstimacionFinStock?.ToString("dd/MM/yyyy") ?? "N/A");
}
var totalBobinas = Model.Existencias.Sum(e => e.BobinasEnStock ?? 0);
var totalKilos = Model.Existencias.Sum(e => e.TotalKilosEnStock ?? 0);
var totalConsumo = Model.Existencias.Sum(e => e.ConsumoAcumulado ?? 0);
table.Cell().BorderTop(1).BorderColor(Colors.Grey.Lighten2).Padding(4).AlignRight().Text(t => t.Span("Totales").SemiBold());
table.Cell().BorderTop(1).BorderColor(Colors.Grey.Lighten2).Padding(4).AlignRight().Text(t => t.Span(totalBobinas.ToString("N0")).SemiBold());
table.Cell().BorderTop(1).BorderColor(Colors.Grey.Lighten2).Padding(4).AlignRight().Text(t => t.Span(totalKilos.ToString("N0")).SemiBold());
table.Cell().BorderTop(1).BorderColor(Colors.Grey.Lighten2).Padding(4).AlignRight().Text(t => t.Span(totalConsumo.ToString("N0")).SemiBold());
table.Cell().ColumnSpan(2).BorderTop(1).BorderColor(Colors.Grey.Lighten2);
});
}
}
}

View File

@@ -0,0 +1,117 @@
using GestionIntegral.Api.Dtos.Reportes.ViewModels;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using System.Linq;
namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
{
// Usamos una clase parcial para organizar mejor el código, aunque no es estrictamente necesario aquí.
public class ExistenciaPapelDocument : IDocument
{
// Usamos una propiedad pública con 'init' en lugar de un campo privado.
// Esto es más limpio y funciona mejor con el análisis de código.
public ExistenciaPapelViewModel Model { get; }
public ExistenciaPapelDocument(ExistenciaPapelViewModel model)
{
Model = model;
}
public DocumentMetadata GetMetadata() => DocumentMetadata.Default;
public DocumentSettings GetSettings() => DocumentSettings.Default;
public void Compose(IDocumentContainer container)
{
container
.Page(page =>
{
page.Margin(1, Unit.Centimetre);
page.PageColor(Colors.White);
page.DefaultTextStyle(x => x.FontFamily("Roboto").FontSize(10));
page.Header().Element(ComposeHeader);
page.Content().Element(ComposeContent);
page.Footer()
.AlignCenter()
.Text(x =>
{
x.Span("Página ");
x.CurrentPageNumber();
});
});
}
// Ahora los métodos usan 'Model' (la propiedad pública) en lugar de '_model'.
void ComposeHeader(IContainer container)
{
container.Column(column =>
{
column.Spacing(5);
column.Item().AlignCenter().Text("Reporte de Existencias de Papel").SemiBold().FontSize(14);
column.Item().AlignCenter().Text($"Planta: {Model.NombrePlanta}").FontSize(12);
column.Item().PaddingTop(2, Unit.Millimetre).Row(row =>
{
row.RelativeItem().Column(col =>
{
col.Item().Text(text =>
{
text.Span("Fecha del Reporte: ").SemiBold();
text.Span(Model.FechaReporte);
});
col.Item().Text($"Periodo Consultado: Desde {Model.FechaDesde} Hasta {Model.FechaHasta}");
});
});
});
}
void ComposeContent(IContainer container)
{
container.PaddingTop(1, Unit.Centimetre).Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn();
columns.ConstantColumn(65);
columns.ConstantColumn(70);
columns.ConstantColumn(85);
columns.ConstantColumn(65);
columns.ConstantColumn(80);
});
table.Header(header =>
{
header.Cell().Background(Colors.Grey.Lighten3).Padding(4).Text("Tipo de Bobina");
header.Cell().Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Cant. Stock");
header.Cell().Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Kg. Stock");
header.Cell().Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Consumo (Kg)");
header.Cell().Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Días Disp.");
header.Cell().Background(Colors.Grey.Lighten3).Padding(4).AlignCenter().Text("Fin Estimado");
});
foreach (var item in Model.Existencias)
{
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(4).Text(item.TipoBobina);
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(4).AlignRight().Text(item.BobinasEnStock?.ToString("N0") ?? "0");
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(4).AlignRight().Text(item.TotalKilosEnStock?.ToString("N0") ?? "0");
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(4).AlignRight().Text(item.ConsumoAcumulado?.ToString("N0") ?? "0");
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(4).AlignRight().Text(item.PromedioDiasDisponibles?.ToString("N0") ?? "N/A");
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(4).AlignCenter().Text(item.FechaEstimacionFinStock?.ToString("dd/MM/yyyy") ?? "N/A");
}
var totalBobinas = Model.Existencias.Sum(e => e.BobinasEnStock ?? 0);
var totalKilos = Model.Existencias.Sum(e => e.TotalKilosEnStock ?? 0);
var totalConsumo = Model.Existencias.Sum(e => e.ConsumoAcumulado ?? 0);
table.Cell().BorderTop(1).BorderColor(Colors.Grey.Lighten2).Padding(4).AlignRight().Text(t => t.Span("Totales").SemiBold());
table.Cell().BorderTop(1).BorderColor(Colors.Grey.Lighten2).Padding(4).AlignRight().Text(t => t.Span(totalBobinas.ToString("N0")).SemiBold());
table.Cell().BorderTop(1).BorderColor(Colors.Grey.Lighten2).Padding(4).AlignRight().Text(t => t.Span(totalKilos.ToString("N0")).SemiBold());
table.Cell().BorderTop(1).BorderColor(Colors.Grey.Lighten2).Padding(4).AlignRight().Text(t => t.Span(totalConsumo.ToString("N0")).SemiBold());
table.Cell().ColumnSpan(2).BorderTop(1).BorderColor(Colors.Grey.Lighten2);
});
}
}
}

View File

@@ -0,0 +1,121 @@
using GestionIntegral.Api.Dtos.Reportes.ViewModels;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using System.Globalization;
using System.Linq;
namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
{
public class FacturasPublicidadDocument : IDocument
{
public FacturasPublicidadViewModel Model { get; }
public FacturasPublicidadDocument(FacturasPublicidadViewModel model)
{
Model = model;
}
public DocumentMetadata GetMetadata() => DocumentMetadata.Default;
public void Compose(IDocumentContainer container)
{
container.Page(page =>
{
page.Margin(1, Unit.Centimetre);
page.DefaultTextStyle(x => x.FontFamily("Arial").FontSize(9));
page.Header().Element(ComposeHeader);
page.Content().Element(ComposeContent);
page.Footer().AlignCenter().Text(x => { x.Span("Página "); x.CurrentPageNumber(); });
});
}
void ComposeHeader(IContainer container)
{
// Se envuelve todo el contenido del header en una única Columna.
container.Column(column =>
{
// El primer item de la columna es la fila con los títulos.
column.Item().Row(row =>
{
row.RelativeItem().Column(col =>
{
col.Item().Text($"Reporte de Suscripciones a Facturar").SemiBold().FontSize(14);
col.Item().Text($"Período: {Model.Periodo}").FontSize(11);
});
row.ConstantItem(150).AlignRight().Column(col => {
col.Item().AlignRight().Text($"Fecha de Generación:");
col.Item().AlignRight().Text(Model.FechaGeneracion);
});
});
// El segundo item de la columna es el separador.
column.Item().PaddingTop(5).BorderBottom(1).BorderColor(Colors.Grey.Lighten2);
});
}
void ComposeContent(IContainer container)
{
container.PaddingTop(10).Column(column =>
{
column.Spacing(20);
foreach (var empresaData in Model.DatosPorEmpresa)
{
column.Item().Element(c => ComposeTablaPorEmpresa(c, empresaData));
}
column.Item().AlignRight().PaddingTop(15).Text($"Total General a Facturar: {Model.TotalGeneral.ToString("C", new CultureInfo("es-AR"))}").Bold().FontSize(12);
});
}
void ComposeTablaPorEmpresa(IContainer container, DatosEmpresaViewModel empresaData)
{
container.Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn(3); // Nombre Suscriptor
columns.ConstantColumn(100); // Documento
columns.ConstantColumn(100, Unit.Point); // Importe
});
table.Header(header =>
{
header.Cell().ColumnSpan(3).Background(Colors.Grey.Lighten2)
.Padding(5).Text(empresaData.NombreEmpresa).Bold().FontSize(12);
header.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten3).Padding(2).Text("Suscriptor").SemiBold();
header.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten3).Padding(2).Text("Documento").SemiBold();
header.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Importe a Facturar").SemiBold();
});
var facturasPorSuscriptor = empresaData.Facturas.GroupBy(f => f.NombreSuscriptor);
foreach (var grupoSuscriptor in facturasPorSuscriptor.OrderBy(g => g.Key))
{
foreach(var item in grupoSuscriptor)
{
table.Cell().Padding(2).Text(item.NombreSuscriptor);
table.Cell().Padding(2).Text($"{item.TipoDocumento} {item.NroDocumento}");
table.Cell().Padding(2).AlignRight().Text(item.ImporteFinal.ToString("C", new CultureInfo("es-AR")));
}
if(grupoSuscriptor.Count() > 1)
{
var subtotal = grupoSuscriptor.Sum(i => i.ImporteFinal);
table.Cell().ColumnSpan(2).AlignRight().Padding(2).Text($"Subtotal {grupoSuscriptor.Key}:").Italic();
table.Cell().AlignRight().Padding(2).Text(subtotal.ToString("C", new CultureInfo("es-AR"))).Italic().SemiBold();
}
}
table.Cell().ColumnSpan(2).BorderTop(1).BorderColor(Colors.Grey.Darken1).AlignRight()
.PaddingTop(5).Text("Total Empresa:").Bold();
table.Cell().BorderTop(1).BorderColor(Colors.Grey.Darken1).AlignRight()
.PaddingTop(5).Text(empresaData.TotalEmpresa.ToString("C", new CultureInfo("es-AR"))).Bold();
});
}
}
}

View File

@@ -0,0 +1,136 @@
using GestionIntegral.Api.Dtos.Reportes.ViewModels;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using System.Globalization;
using System.Linq;
namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
{
public class LiquidacionCanillaDocument : IDocument
{
public LiquidacionCanillaViewModel Model { get; }
private static readonly CultureInfo CultureAr = new CultureInfo("es-AR");
public LiquidacionCanillaDocument(LiquidacionCanillaViewModel model)
{
Model = model;
}
public DocumentMetadata GetMetadata() => DocumentMetadata.Default;
// CORRECCIÓN: El método GetSettings ya no es necesario para este diseño.
// La configuración por defecto es suficiente.
// public DocumentSettings GetSettings() => DocumentSettings.Default;
public void Compose(IDocumentContainer container)
{
container.Page(page =>
{
page.Size(PageSizes.A5);
page.Margin(1, Unit.Centimetre);
page.DefaultTextStyle(x => x.FontFamily("Arial").FontSize(9));
page.Header().Element(ComposeHeader);
page.Content().Element(ComposeContent);
});
}
void ComposeHeader(IContainer container)
{
container.Column(column =>
{
column.Item().Text("EL DIA S.A.I.C. y F.");
column.Item().Text(text =>
{
text.Span("Liquidación venta de diarios del: ");
text.Span(Model.FechaLiquidacion);
});
column.Item().PaddingTop(5).Text($"Vendedor: {Model.NombreVendedor}").SemiBold();
column.Item().PaddingTop(10);
});
}
void ComposeContent(IContainer container)
{
container.Column(column =>
{
column.Spacing(15);
column.Item().Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn(2);
columns.RelativeColumn(2);
columns.RelativeColumn(1);
});
foreach (var item in Model.Detalles.OrderBy(d => d.Publicacion))
{
var vendidos = item.TotalCantSalida - item.TotalCantEntrada;
table.Cell().ColumnSpan(3).Text(item.Publicacion).SemiBold();
table.Cell();
table.Cell().Text("Retirados");
table.Cell().AlignRight().Text(item.TotalCantSalida.ToString("N0"));
table.Cell();
table.Cell().Text("Devueltos");
table.Cell().AlignRight().Text(item.TotalCantEntrada.ToString("N0"));
table.Cell();
table.Cell().Text("Vendidos");
table.Cell().AlignRight().Text(vendidos.ToString("N0"));
table.Cell();
table.Cell().Text("Precio Unitario");
table.Cell().AlignRight().Text(item.PrecioEjemplar.ToString("C", CultureAr));
table.Cell();
table.Cell().BorderTop(1.5f).BorderColor(Colors.Black).PaddingTop(2).Text(t => t.Span("Importe Vendido").SemiBold());
table.Cell().BorderTop(1.5f).BorderColor(Colors.Black).PaddingTop(2).AlignRight().Text(t => t.Span(item.TotalRendir.ToString("C", CultureAr)).SemiBold());
}
});
column.Item().BorderTop(2).BorderColor(Colors.Black).PaddingTop(4).Row(row =>
{
row.RelativeItem().Text("Total A Rendir").SemiBold().FontSize(10);
row.RelativeItem().AlignRight().Text(text => text.Span(Model.TotalARendir.ToString("C", CultureAr)).SemiBold().FontSize(10));
});
if (!Model.EsAccionista && Model.Ganancias.Any())
{
column.Item().PaddingTop(10).Element(ComposeGananciasTable);
}
});
}
void ComposeGananciasTable(IContainer container)
{
container.Column(column =>
{
column.Item().Text("Comisiones Acreditadas").SemiBold().FontSize(11);
column.Item().PaddingTop(5).Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn(2);
columns.RelativeColumn(1);
});
foreach (var item in Model.Ganancias)
{
table.Cell().Border(1).BorderColor(Colors.Grey.Lighten2).Padding(3).Text(item.Publicacion);
table.Cell().Border(1).BorderColor(Colors.Grey.Lighten2).Padding(3).AlignRight().Text(item.TotalRendir.ToString("C", CultureAr));
}
table.Cell().BorderTop(1.5f).BorderColor(Colors.Black).Padding(3).Text("Total Comisiones").SemiBold();
table.Cell().BorderTop(1.5f).BorderColor(Colors.Black).Padding(3).AlignRight().Text(t => t.Span(Model.TotalComisiones.ToString("C", CultureAr)).SemiBold());
});
});
}
}
}

View File

@@ -0,0 +1,99 @@
using GestionIntegral.Api.Dtos.Reportes.ViewModels;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using System.Globalization;
using System.Linq;
namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
{
public class ListadoDistCanMensualDiariosDocument : IDocument
{
public ListadoDistCanMensualDiariosViewModel Model { get; }
private static readonly CultureInfo CultureAr = new CultureInfo("es-AR");
public ListadoDistCanMensualDiariosDocument(ListadoDistCanMensualDiariosViewModel model)
{
Model = model;
}
public DocumentMetadata GetMetadata() => DocumentMetadata.Default;
public void Compose(IDocumentContainer container)
{
container.Page(page =>
{
page.Margin(1, Unit.Centimetre);
page.DefaultTextStyle(x => x.FontFamily("Arial").FontSize(9));
page.Header().Element(ComposeHeader);
page.Content().Element(ComposeContent);
page.Footer().AlignCenter().Text(x => { x.Span("Página "); x.CurrentPageNumber(); });
});
}
void ComposeHeader(IContainer container)
{
container.Column(column =>
{
column.Item().AlignCenter().Text($"Ventas por {Model.TipoDestinatario} desde el {Model.FechaDesde} hasta el {Model.FechaHasta}").SemiBold().FontSize(12);
});
}
void ComposeContent(IContainer container)
{
// APLICAMOS LA SOLUCIÓN AQUÍ: .AlignTop()
container.PaddingTop(1, Unit.Centimetre).AlignTop().Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn(3); // Nombre
columns.ConstantColumn(50); // El Día
columns.ConstantColumn(50); // El Plata
columns.ConstantColumn(60); // Vendidos
columns.RelativeColumn(1.5f); // Imp. El Día
columns.RelativeColumn(1.5f); // Imp. El Plata
columns.RelativeColumn(1.5f); // Importe Total
});
table.Header(header =>
{
header.Cell().BorderBottom(1).BorderColor(Colors.Grey.Medium).Padding(4).Text("Nombre").SemiBold();
header.Cell().BorderBottom(1).BorderColor(Colors.Grey.Medium).Padding(4).AlignCenter().Text("El Día").SemiBold();
header.Cell().BorderBottom(1).BorderColor(Colors.Grey.Medium).Padding(4).AlignCenter().Text("El Plata").SemiBold();
header.Cell().BorderBottom(1).BorderColor(Colors.Grey.Medium).Padding(4).AlignCenter().Text("Vendidos").SemiBold();
header.Cell().BorderBottom(1).BorderColor(Colors.Grey.Medium).Padding(4).AlignRight().Text("Imp. El Día").SemiBold();
header.Cell().BorderBottom(1).BorderColor(Colors.Grey.Medium).Padding(4).AlignRight().Text("Imp. El Plata").SemiBold();
header.Cell().BorderBottom(1).BorderColor(Colors.Grey.Medium).Padding(4).AlignRight().Text("Importe Total").SemiBold();
});
foreach (var item in Model.Detalles.OrderBy(d => d.Canilla))
{
table.Cell().Padding(3).PaddingRight(10).Text(item.Canilla);
table.Cell().Padding(3).AlignCenter().Text(item.ElDia?.ToString("N0") ?? "0");
table.Cell().Padding(3).AlignCenter().Text(item.ElPlata?.ToString("N0") ?? "0");
table.Cell().Padding(3).AlignCenter().Text(item.Vendidos?.ToString("N0") ?? "0");
table.Cell().Padding(3).AlignRight().Text(item.ImporteElDia?.ToString("C", CultureAr) ?? "$ 0.00");
table.Cell().Padding(3).AlignRight().Text(item.ImporteElPlata?.ToString("C", CultureAr) ?? "$ 0.00");
table.Cell().Padding(3).AlignRight().Text(item.ImporteTotal?.ToString("C", CultureAr) ?? "$ 0.00");
}
// Fila de Totales
var totalElDia = Model.Detalles.Sum(x => x.ElDia ?? 0);
var totalElPlata = Model.Detalles.Sum(x => x.ElPlata ?? 0);
var totalVendidos = Model.Detalles.Sum(x => x.Vendidos ?? 0);
var totalImpElDia = Model.Detalles.Sum(x => x.ImporteElDia ?? 0);
var totalImpElPlata = Model.Detalles.Sum(x => x.ImporteElPlata ?? 0);
var totalImpTotal = Model.Detalles.Sum(x => x.ImporteTotal ?? 0);
table.Cell().BorderTop(1.5f).BorderColor(Colors.Black).PaddingTop(4).Text("Totales").SemiBold();
table.Cell().BorderTop(1.5f).BorderColor(Colors.Black).PaddingTop(4).AlignCenter().Text(t => t.Span(totalElDia.ToString("N0")).SemiBold());
table.Cell().BorderTop(1.5f).BorderColor(Colors.Black).PaddingTop(4).AlignCenter().Text(t => t.Span(totalElPlata.ToString("N0")).SemiBold());
table.Cell().BorderTop(1.5f).BorderColor(Colors.Black).PaddingTop(4).AlignCenter().Text(t => t.Span(totalVendidos.ToString("N0")).SemiBold());
table.Cell().BorderTop(1.5f).BorderColor(Colors.Black).PaddingTop(4).AlignRight().Text(t => t.Span(totalImpElDia.ToString("C", CultureAr)).SemiBold());
table.Cell().BorderTop(1.5f).BorderColor(Colors.Black).PaddingTop(4).AlignRight().Text(t => t.Span(totalImpElPlata.ToString("C", CultureAr)).SemiBold());
table.Cell().BorderTop(1.5f).BorderColor(Colors.Black).PaddingTop(4).AlignRight().Text(t => t.Span(totalImpTotal.ToString("C", CultureAr)).SemiBold());
});
}
}
}

View File

@@ -0,0 +1,113 @@
using GestionIntegral.Api.Dtos.Reportes.ViewModels;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using System.Globalization;
using System.Linq;
namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
{
public class ListadoDistCanMensualDocument : IDocument
{
public ListadoDistCanMensualViewModel Model { get; }
private static readonly CultureInfo CultureAr = new CultureInfo("es-AR");
public ListadoDistCanMensualDocument(ListadoDistCanMensualViewModel model)
{
Model = model;
}
public DocumentMetadata GetMetadata() => DocumentMetadata.Default;
public void Compose(IDocumentContainer container)
{
container.Page(page =>
{
page.Margin(1.5f, Unit.Centimetre);
page.DefaultTextStyle(x => x.FontFamily("Arial").FontSize(10));
page.Header().Element(ComposeHeader);
page.Content().Element(ComposeContent);
page.Footer().AlignCenter().Text(x => { x.Span("Página "); x.CurrentPageNumber(); });
});
}
void ComposeHeader(IContainer container)
{
container.Column(column =>
{
column.Item().AlignCenter().Text("Listado de Distribución Mensual").SemiBold().FontSize(14);
column.Item().AlignCenter().Text(Model.TipoDestinatario).FontSize(12);
column.Item().PaddingTop(5, Unit.Millimetre).Row(row =>
{
row.RelativeItem().Text(text => { text.Span("Fecha de Reporte: ").SemiBold(); text.Span(Model.FechaReporte); });
row.RelativeItem().AlignRight().Text(text => { text.Span("Periodo: ").SemiBold(); text.Span($"{Model.FechaDesde} - {Model.FechaHasta}"); });
});
});
}
void ComposeContent(IContainer container)
{
container.PaddingTop(1, Unit.Centimetre).Column(column =>
{
column.Spacing(10);
// Agrupamos los datos por Canilla
var gruposPorCanilla = Model.Detalles.GroupBy(d => d.Canilla);
// Creamos una tabla principal que contendrá todo
column.Item().Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn(2.5f); // Canilla / Publicación
columns.RelativeColumn(); // Llevados
columns.RelativeColumn(); // Devueltos
columns.RelativeColumn(1.2f); // Importe
});
// Encabezado principal de la tabla
table.Header(header =>
{
header.Cell().BorderBottom(1.5f).BorderColor(Colors.Black).Padding(4).Text("Canilla / Publicación").SemiBold();
header.Cell().BorderBottom(1.5f).BorderColor(Colors.Black).Padding(4).AlignRight().Text("Llevados").SemiBold();
header.Cell().BorderBottom(1.5f).BorderColor(Colors.Black).Padding(4).AlignRight().Text("Devueltos").SemiBold();
header.Cell().BorderBottom(1.5f).BorderColor(Colors.Black).Padding(4).AlignRight().Text("Importe").SemiBold();
});
// Iteramos sobre cada grupo de canilla
foreach (var grupo in gruposPorCanilla)
{
// Fila del nombre del Canilla
table.Cell().ColumnSpan(4).PaddingTop(8).Text(grupo.Key).SemiBold();
// Filas de detalle para ese canilla
foreach (var detalle in grupo)
{
table.Cell().PaddingLeft(15).Padding(2).Text(detalle.Publicacion);
table.Cell().Padding(2).AlignRight().Text(detalle.TotalCantSalida?.ToString("N0") ?? "0");
table.Cell().Padding(2).AlignRight().Text(detalle.TotalCantEntrada?.ToString("N0") ?? "0");
table.Cell().Padding(2).AlignRight().Text(detalle.TotalRendir?.ToString("C", CultureAr) ?? "$ 0.00");
}
// Fila de total por canilla
var totalRendirCanilla = grupo.Sum(d => d.TotalRendir ?? 0);
table.Cell().ColumnSpan(3).BorderTop(1.5f).BorderColor(Colors.Black).AlignRight().Padding(2).Text("A Rendir").SemiBold();
table.Cell().BorderTop(1.5f).BorderColor(Colors.Black).AlignRight().Padding(2).Text(t => t.Span(totalRendirCanilla.ToString("C", CultureAr)).SemiBold());
}
// --- Fila de TOTALES GENERALES ---
var totalGeneralLlevados = Model.Detalles.Sum(d => d.TotalCantSalida ?? 0);
var totalGeneralDevueltos = Model.Detalles.Sum(d => d.TotalCantEntrada ?? 0);
var totalGeneralRendir = Model.Detalles.Sum(d => d.TotalRendir ?? 0);
table.Cell().BorderTop(2).BorderColor(Colors.Black).PaddingTop(5).Text("TOTALES").ExtraBold();
table.Cell().BorderTop(2).BorderColor(Colors.Black).PaddingTop(5).AlignRight().Text(t => t.Span(totalGeneralLlevados.ToString("N0")).ExtraBold());
table.Cell().BorderTop(2).BorderColor(Colors.Black).PaddingTop(5).AlignRight().Text(t => t.Span(totalGeneralDevueltos.ToString("N0")).ExtraBold());
table.Cell().BorderTop(2).BorderColor(Colors.Black).PaddingTop(5).AlignRight().Text(t => t.Span(totalGeneralRendir.ToString("C", CultureAr)).ExtraBold());
});
});
}
}
}

View File

@@ -0,0 +1,103 @@
using GestionIntegral.Api.Dtos.Reportes.ViewModels;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using System.Globalization;
using System.Linq;
namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
{
public class ListadoDistCanillasImporteDocument : IDocument
{
public ListadoDistCanillasImporteViewModel Model { get; }
private static readonly CultureInfo CultureAr = new CultureInfo("es-AR");
public ListadoDistCanillasImporteDocument(ListadoDistCanillasImporteViewModel model)
{
Model = model;
}
public DocumentMetadata GetMetadata() => DocumentMetadata.Default;
public void Compose(IDocumentContainer container)
{
container.Page(page =>
{
page.Margin(1.5f, Unit.Centimetre);
page.DefaultTextStyle(x => x.FontFamily("Arial").FontSize(10));
page.Header().Element(ComposeHeader);
page.Content().Element(ComposeContent);
page.Footer().AlignCenter().Text(x => { x.Span("Página "); x.CurrentPageNumber(); });
});
}
void ComposeHeader(IContainer container)
{
container.Column(column =>
{
column.Item().AlignCenter().Text("Listado de Distribución con Importes").SemiBold().FontSize(14);
column.Item().AlignCenter().Text(Model.TipoDestinatario).FontSize(12);
column.Item().AlignCenter().Text(Model.NombrePublicacion).FontSize(12);
column.Item().PaddingTop(5, Unit.Millimetre).Row(row =>
{
row.RelativeItem().Text(text => { text.Span("Fecha de Reporte: ").SemiBold(); text.Span(Model.FechaReporte); });
row.RelativeItem().AlignRight().Text(text => { text.Span("Periodo: ").SemiBold(); text.Span($"{Model.FechaDesde} - {Model.FechaHasta}"); });
});
});
}
void ComposeContent(IContainer container)
{
container.PaddingTop(1, Unit.Centimetre).Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn(); // Fecha
columns.RelativeColumn(); // Llevados
columns.RelativeColumn(); // Devueltos
columns.RelativeColumn(); // Vendidos
columns.RelativeColumn(1.5f); // Importe Publicación
columns.RelativeColumn(1.5f); // A Rendir
});
table.Header(header =>
{
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).Text("Fecha");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Llevados");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Devueltos");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Vendidos");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Imp. Publicación");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("A Rendir");
});
foreach (var item in Model.Detalles)
{
table.Cell().Border(1).Padding(3).Text(item.Fecha);
table.Cell().Border(1).Padding(3).AlignRight().Text(item.Llevados.ToString("N0"));
table.Cell().Border(1).Padding(3).AlignRight().Text(item.Devueltos.ToString("N0"));
table.Cell().Border(1).Padding(3).AlignRight().Text(item.Vendidos.ToString("N0"));
table.Cell().Border(1).Padding(3).AlignRight().Text(item.TotalRendirPublicacion.ToString("C", CultureAr));
table.Cell().Border(1).Padding(3).AlignRight().Text(item.TotalRendirGeneral.ToString("C", CultureAr));
}
// Fila de Totales
var totalLlevados = Model.Detalles.Sum(x => x.Llevados);
var totalDevueltos = Model.Detalles.Sum(x => x.Devueltos);
var totalVendidos = Model.Detalles.Sum(x => x.Vendidos);
var totalRendirPub = Model.Detalles.Sum(x => x.TotalRendirPublicacion);
var totalRendirGral = Model.Detalles.Sum(x => x.TotalRendirGeneral);
var boldStyle = TextStyle.Default.SemiBold();
table.Cell().Border(1).Padding(3); // Celda vacía
table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(totalLlevados.ToString("N0")).Style(boldStyle));
table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(totalDevueltos.ToString("N0")).Style(boldStyle));
table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(totalVendidos.ToString("N0")).Style(boldStyle));
table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(totalRendirPub.ToString("C", CultureAr)).Style(boldStyle));
table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(totalRendirGral.ToString("C", CultureAr)).Style(boldStyle));
});
}
}
}

View File

@@ -0,0 +1,179 @@
using GestionIntegral.Api.Dtos.Reportes.ViewModels;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using System.Collections.Generic;
using System.Linq;
namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
{
public class ListadoDistribucionCanillasDocument : IDocument
{
public ListadoDistribucionCanillasViewModel Model { get; }
public ListadoDistribucionCanillasDocument(ListadoDistribucionCanillasViewModel model)
{
Model = model;
}
public DocumentMetadata GetMetadata() => DocumentMetadata.Default;
public void Compose(IDocumentContainer container)
{
container.Page(page =>
{
page.Margin(1, Unit.Centimetre);
page.DefaultTextStyle(x => x.FontFamily("Arial").FontSize(10));
page.Header().Element(ComposeHeader);
page.Content().Element(ComposeContent);
page.Footer().AlignCenter().Text(x => { x.Span("Página "); x.CurrentPageNumber(); });
});
}
void ComposeHeader(IContainer container)
{
container.Column(column =>
{
column.Item().AlignCenter().Text("Listado de Distribución - Canillitas").SemiBold().FontSize(14);
column.Item().AlignCenter().Text(Model.NombrePublicacion).FontSize(12);
column.Item().PaddingTop(5, Unit.Millimetre).Row(row =>
{
row.RelativeItem().Text(text => { text.Span("Fecha de Reporte: ").SemiBold(); text.Span(Model.FechaReporte); });
row.RelativeItem().AlignRight().Text(text => { text.Span("Periodo: ").SemiBold(); text.Span($"{Model.FechaDesde} - {Model.FechaHasta}"); });
});
});
}
void ComposeContent(IContainer container)
{
container.PaddingTop(8, Unit.Millimetre).Column(column =>
{
column.Spacing(15);
column.Item().Text("Distribución").SemiBold().FontSize(12);
column.Item().Element(ComposeDetalleDiarioTable);
column.Item().PaddingTop(5, Unit.Millimetre);
column.Item().Text("Promedios").SemiBold().FontSize(12);
column.Item().Element(ComposePromediosTable);
});
}
void ComposeDetalleDiarioTable(IContainer container)
{
container.Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.ConstantColumn(50); // Día
columns.RelativeColumn(); // Llevados
columns.RelativeColumn(); // Devueltos
columns.RelativeColumn(); // Venta Neta
columns.RelativeColumn(2); // Promedio (columna vacía en el original)
columns.RelativeColumn(2); // % Devolución
});
table.Header(header =>
{
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).Text("Día");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Llevados");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Devueltos");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Venta Neta");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Promedio");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("% Devolución");
});
decimal ventaNetaAcumulada = 0;
int conteoDias = 0;
foreach (var item in Model.DetalleDiario.OrderBy(x => x.Dia))
{
var ventaNetaDia = item.Llevados - item.Devueltos;
ventaNetaAcumulada += ventaNetaDia;
conteoDias++;
var promedio = ventaNetaAcumulada / conteoDias;
var porcDevolucion = item.Llevados > 0 ? (decimal)item.Devueltos * 100 / item.Llevados : 0;
table.Cell().Border(1).Padding(3).Text(item.Dia.ToString());
table.Cell().Border(1).Padding(3).AlignRight().Text(item.Llevados.ToString("N0"));
table.Cell().Border(1).Padding(3).AlignRight().Text(item.Devueltos.ToString("N0"));
table.Cell().Border(1).Padding(3).AlignRight().Text(ventaNetaDia.ToString("N0"));
table.Cell().Border(1).Padding(3).AlignRight().Text(promedio.ToString("N0"));
table.Cell().Border(1).Padding(3).AlignRight().Text(porcDevolucion.ToString("F2"));
}
var totalVentaNeta = Model.TotalesDetalleDiario.Llevados - Model.TotalesDetalleDiario.Devueltos;
var totalPorcDevolucion = Model.TotalesDetalleDiario.Llevados > 0 ? (decimal)Model.TotalesDetalleDiario.Devueltos * 100 / Model.TotalesDetalleDiario.Llevados : 0;
table.Cell().Border(1).Padding(3); // Celda vacía
table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(Model.TotalesDetalleDiario.Llevados.ToString("N0")).SemiBold());
table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(Model.TotalesDetalleDiario.Devueltos.ToString("N0")).SemiBold());
table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(totalVentaNeta.ToString("N0")).SemiBold());
table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span((ventaNetaAcumulada / (conteoDias > 0 ? conteoDias : 1)).ToString("N0")).SemiBold());
table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(totalPorcDevolucion.ToString("F2")).SemiBold());
});
}
void ComposePromediosTable(IContainer container)
{
container.Column(column =>
{
column.Item().PaddingBottom(5).Text("Promedios por Día de Semana").SemiBold().FontSize(11);
column.Item().Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn(1.5f);
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn(1.2f);
});
table.Header(header =>
{
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).Text("Día Semana");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Cant. Días");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Prom. Llevados");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Prom. Devueltos");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Prom. Ventas");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("% Devolución");
});
var dayOrder = new Dictionary<string, int> { { "Lunes", 1 }, { "Martes", 2 }, { "Miércoles", 3 }, { "Jueves", 4 }, { "Viernes", 5 }, { "Sábado", 6 }, { "Domingo", 7 } };
foreach (var item in Model.PromediosPorDia.OrderBy(d => dayOrder.GetValueOrDefault(d.Dia, 99)))
{
var porcDevolucion = item.Llevados > 0 ? (decimal)item.Devueltos * 100 / item.Llevados : 0;
table.Cell().Border(1).Padding(3).Text(item.Dia);
table.Cell().Border(1).Padding(3).AlignRight().Text(item.Cant.ToString("N0"));
table.Cell().Border(1).Padding(3).AlignRight().Text(item.Promedio_Llevados.ToString("N0"));
table.Cell().Border(1).Padding(3).AlignRight().Text(item.Promedio_Devueltos.ToString("N0"));
table.Cell().Border(1).Padding(3).AlignRight().Text(item.Promedio_Ventas.ToString("N0"));
table.Cell().Border(1).Padding(3).AlignRight().Text(porcDevolucion.ToString("F2") + "%");
}
// --- SECCIÓN AÑADIDA PARA LA FILA "GENERAL" ---
var general = Model.PromedioGeneral;
if (general != null)
{
var porcDevolucionGeneral = general.Promedio_Llevados > 0 ? (decimal)general.Promedio_Devueltos * 100 / general.Promedio_Llevados : 0;
var boldStyle = TextStyle.Default.SemiBold();
table.Cell().Border(1).Padding(3).Text(text => text.Span(general.Dia).Style(boldStyle));
table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(general.Cant.ToString("N0")).Style(boldStyle));
table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(general.Promedio_Llevados.ToString("N0")).Style(boldStyle));
table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(general.Promedio_Devueltos.ToString("N0")).Style(boldStyle));
table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(general.Promedio_Ventas.ToString("N0")).Style(boldStyle));
table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(porcDevolucionGeneral.ToString("F2") + "%").Style(boldStyle));
}
});
});
}
}
}

View File

@@ -0,0 +1,178 @@
using GestionIntegral.Api.Dtos.Reportes.ViewModels;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using System.Collections.Generic;
using System.Linq;
namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
{
public class ListadoDistribucionDistribuidoresDocument : IDocument
{
public ListadoDistribucionDistribuidoresViewModel Model { get; }
public ListadoDistribucionDistribuidoresDocument(ListadoDistribucionDistribuidoresViewModel model)
{
Model = model;
}
public DocumentMetadata GetMetadata() => DocumentMetadata.Default;
public void Compose(IDocumentContainer container)
{
container.Page(page =>
{
page.Margin(1, Unit.Centimetre);
page.DefaultTextStyle(x => x.FontFamily("Arial").FontSize(10));
page.Header().Element(ComposeHeader);
page.Content().Element(ComposeContent);
page.Footer().AlignCenter().Text(x => { x.Span("Página "); x.CurrentPageNumber(); });
});
}
void ComposeHeader(IContainer container)
{
container.Column(column =>
{
column.Item().AlignCenter().Text("Listado de Distribución").SemiBold().FontSize(14);
column.Item().AlignCenter().Text(Model.NombreDistribuidor).FontSize(12);
column.Item().AlignCenter().Text(Model.NombrePublicacion).FontSize(11);
column.Item().PaddingTop(5, Unit.Millimetre).Row(row =>
{
row.RelativeItem().Text(text => { text.Span("Fecha de Reporte: ").SemiBold(); text.Span(Model.FechaReporte); });
row.RelativeItem().AlignRight().Text(text => { text.Span("Periodo: ").SemiBold(); text.Span($"{Model.FechaDesde} - {Model.FechaHasta}"); });
});
});
}
void ComposeContent(IContainer container)
{
container.PaddingTop(8, Unit.Millimetre).Column(column =>
{
column.Spacing(15);
column.Item().Text("Distribución").SemiBold().FontSize(12);
column.Item().Element(ComposeDetalleDiarioTable);
column.Item().PaddingTop(5, Unit.Millimetre);
column.Item().Text("Promedios").SemiBold().FontSize(12);
column.Item().Element(ComposePromediosTable);
});
}
void ComposeDetalleDiarioTable(IContainer container)
{
container.Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.ConstantColumn(50); columns.RelativeColumn();
columns.RelativeColumn(); columns.RelativeColumn();
columns.RelativeColumn(2); columns.RelativeColumn(2);
});
table.Header(header =>
{
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).Text("Día");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Llevados");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Devueltos");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Venta Neta");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Promedio");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("% Devolución");
});
decimal ventaNetaAcumulada = 0;
int conteoDias = 0;
foreach (var item in Model.DetalleDiario.OrderBy(x => x.Dia))
{
var llevados = item.Llevados ?? 0;
var devueltos = item.Devueltos ?? 0;
var ventaNetaDia = llevados - devueltos;
if(llevados > 0)
{
ventaNetaAcumulada += ventaNetaDia;
conteoDias++;
}
var promedio = conteoDias > 0 ? ventaNetaAcumulada / conteoDias : 0;
var porcDevolucion = llevados > 0 ? (decimal)devueltos * 100 / llevados : 0;
table.Cell().Border(1).Padding(3).Text(item.Dia.ToString());
table.Cell().Border(1).Padding(3).AlignRight().Text(llevados.ToString("N0"));
table.Cell().Border(1).Padding(3).AlignRight().Text(devueltos.ToString("N0"));
table.Cell().Border(1).Padding(3).AlignRight().Text(ventaNetaDia.ToString("N0"));
table.Cell().Border(1).Padding(3).AlignRight().Text(promedio.ToString("N0"));
table.Cell().Border(1).Padding(3).AlignRight().Text(porcDevolucion.ToString("F2") + "%");
}
var totalLlevados = Model.DetalleDiario.Sum(i => i.Llevados ?? 0);
var totalDevueltos = Model.DetalleDiario.Sum(i => i.Devueltos ?? 0);
var totalVentaNeta = totalLlevados - totalDevueltos;
var totalPorcDevolucion = totalLlevados > 0 ? (decimal)totalDevueltos * 100 / totalLlevados : 0;
var boldStyle = TextStyle.Default.SemiBold();
table.Cell().Border(1).Padding(3);
table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(totalLlevados.ToString("N0")).Style(boldStyle));
table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(totalDevueltos.ToString("N0")).Style(boldStyle));
table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(totalVentaNeta.ToString("N0")).Style(boldStyle));
table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span((ventaNetaAcumulada / (conteoDias > 0 ? conteoDias : 1)).ToString("N0")).Style(boldStyle));
table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(totalPorcDevolucion.ToString("F2") + "%").Style(boldStyle));
});
}
void ComposePromediosTable(IContainer container)
{
var dayOrder = new Dictionary<string, int> { { "Lunes", 1 }, { "Martes", 2 }, { "Miércoles", 3 }, { "Jueves", 4 }, { "Viernes", 5 }, { "Sábado", 6 }, { "Domingo", 7 }};
container.Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn(1.5f); columns.RelativeColumn();
columns.RelativeColumn(); columns.RelativeColumn();
columns.RelativeColumn(); columns.RelativeColumn(1.2f);
});
table.Header(header =>
{
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).Text("Día");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Cant. Días");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Prom. Llevados");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Prom. Devueltos");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Prom. Ventas");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("% Devolución");
});
foreach (var item in Model.PromediosPorDia.Where(p => p.Dia != "General").OrderBy(d => dayOrder.GetValueOrDefault(d.Dia, 99)))
{
var llevados = item.Promedio_Llevados ?? 0;
var devueltos = item.Promedio_Devueltos ?? 0;
var porcDevolucion = llevados > 0 ? (decimal)devueltos * 100 / llevados : 0;
table.Cell().Border(1).Padding(3).Text(item.Dia);
table.Cell().Border(1).Padding(3).AlignRight().Text(item.Cant?.ToString("N0") ?? "0");
table.Cell().Border(1).Padding(3).AlignRight().Text(llevados.ToString("N0"));
table.Cell().Border(1).Padding(3).AlignRight().Text(devueltos.ToString("N0"));
table.Cell().Border(1).Padding(3).AlignRight().Text(item.Promedio_Ventas?.ToString("N0") ?? "0");
table.Cell().Border(1).Padding(3).AlignRight().Text(porcDevolucion.ToString("F2") + "%");
}
// --- FILA GENERAL ---
var general = Model.PromedioGeneral;
if (general != null)
{
var boldStyle = TextStyle.Default.SemiBold();
var llevadosGeneral = general.Llevados ?? 0; // Usamos el total para el %
var devueltosGeneral = general.Devueltos ?? 0; // Usamos el total para el %
var porcDevolucionGeneral = llevadosGeneral > 0 ? (decimal)devueltosGeneral * 100 / llevadosGeneral : 0;
table.Cell().Border(1).Padding(3).Text(t => t.Span(general.Dia).Style(boldStyle));
table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(general.Cant?.ToString("N0")).Style(boldStyle));
table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(general.Promedio_Llevados?.ToString("N0")).Style(boldStyle));
table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(general.Promedio_Devueltos?.ToString("N0")).Style(boldStyle));
table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(general.Promedio_Ventas?.ToString("N0")).Style(boldStyle));
table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(porcDevolucionGeneral.ToString("F2") + "%").Style(boldStyle));
}
});
}
}
}

View File

@@ -0,0 +1,190 @@
using GestionIntegral.Api.Dtos.Reportes.ViewModels;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using System.Collections.Generic;
using System.Linq;
namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
{
public class ListadoDistribucionGeneralDocument : IDocument
{
public ListadoDistribucionGeneralViewModel Model { get; }
public ListadoDistribucionGeneralDocument(ListadoDistribucionGeneralViewModel model)
{
Model = model;
}
public DocumentMetadata GetMetadata() => DocumentMetadata.Default;
public void Compose(IDocumentContainer container)
{
container.Page(page =>
{
page.Margin(1, Unit.Centimetre);
page.DefaultTextStyle(x => x.FontFamily("Arial").FontSize(10));
page.Header().Element(ComposeHeader);
page.Content().Element(ComposeContent);
page.Footer().AlignCenter().Text(x => { x.Span("Página "); x.CurrentPageNumber(); });
});
}
void ComposeHeader(IContainer container)
{
container.Column(column =>
{
column.Item().AlignCenter().Text("Listado de Distribución General Mensual").SemiBold().FontSize(14);
column.Item().AlignCenter().Text(Model.NombrePublicacion).FontSize(12);
column.Item().PaddingTop(1, Unit.Millimetre).Row(row =>
{
row.RelativeItem().Text(text => { text.Span("Fecha de Reporte: ").SemiBold(); text.Span(Model.FechaReporte); });
row.RelativeItem().AlignRight().Text(text => { text.Span("Mes Consultado: ").SemiBold(); text.Span(Model.MesConsultado); });
});
});
}
void ComposeContent(IContainer container)
{
container.PaddingTop(1, Unit.Millimetre).Column(column =>
{
column.Spacing(20);
if (Model.ResumenMensual.Any())
{
column.Item().Element(ComposeResumenTable);
}
if (Model.PromediosPorDia.Any())
{
column.Item().Element(ComposePromediosTable);
}
});
}
void ComposeResumenTable(IContainer container)
{
container.Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.ConstantColumn(60);
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
});
table.Header(header =>
{
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).Text("Día");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Tirada");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Sin Cargo");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Perdidos");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Llevados");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Devueltos");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Vendidos");
});
var dayAbbreviations = new Dictionary<System.DayOfWeek, string>
{
{ System.DayOfWeek.Sunday, "Dom" },
{ System.DayOfWeek.Monday, "Lun" },
{ System.DayOfWeek.Tuesday, "Mar" },
{ System.DayOfWeek.Wednesday, "Mie" },
{ System.DayOfWeek.Thursday, "Jue" },
{ System.DayOfWeek.Friday, "Vie" },
{ System.DayOfWeek.Saturday, "Sab" }
};
foreach (var item in Model.ResumenMensual.OrderBy(x => x.Fecha))
{
table.Cell().Border(1).Padding(3).Text($"{dayAbbreviations[item.Fecha.DayOfWeek]} {item.Fecha.Day}");
table.Cell().Border(1).Padding(3).AlignRight().Text(item.CantidadTirada.ToString("N0"));
table.Cell().Border(1).Padding(3).AlignRight().Text(item.SinCargo.ToString("N0"));
table.Cell().Border(1).Padding(3).AlignRight().Text(item.Perdidos.ToString("N0"));
table.Cell().Border(1).Padding(3).AlignRight().Text(item.Llevados.ToString("N0"));
table.Cell().Border(1).Padding(3).AlignRight().Text(item.Devueltos.ToString("N0"));
table.Cell().Border(1).Padding(3).AlignRight().Text(item.Vendidos.ToString("N0"));
}
table.Cell().Border(1).Padding(3);
table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(Model.ResumenMensual.Sum(x => x.CantidadTirada).ToString("N0")).SemiBold());
table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(Model.ResumenMensual.Sum(x => x.SinCargo).ToString("N0")).SemiBold());
table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(Model.ResumenMensual.Sum(x => x.Perdidos).ToString("N0")).SemiBold());
table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(Model.ResumenMensual.Sum(x => x.Llevados).ToString("N0")).SemiBold());
table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(Model.ResumenMensual.Sum(x => x.Devueltos).ToString("N0")).SemiBold());
table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(Model.ResumenMensual.Sum(x => x.Vendidos).ToString("N0")).SemiBold());
});
}
void ComposePromediosTable(IContainer container)
{
container.Column(column =>
{
// --- TÍTULO DE LA TABLA DE PROMEDIOS ---
column.Item().PaddingBottom(5).AlignCenter().Text("Promedios Diarios de Distribución").SemiBold();
column.Item().Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn(1.2f);
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
});
table.Header(header =>
{
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).Text("Día");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Cant. Días");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Tirada");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Sin Cargo");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Perdidos");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Llevados");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Devueltos");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Vendidos");
});
var dayOrder = new Dictionary<string, int> { { "Lunes", 1 }, { "Martes", 2 }, { "Miércoles", 3 }, { "Jueves", 4 }, { "Viernes", 5 }, { "Sábado", 6 }, { "Domingo", 7 } };
// Mostramos los promedios por día de la semana
foreach (var item in Model.PromediosPorDia.OrderBy(d => dayOrder.GetValueOrDefault(d.Dia, 99)))
{
table.Cell().Border(1).Padding(3).Text(item.Dia);
table.Cell().Border(1).Padding(3).AlignRight().Text(item.CantidadDias.ToString("N0"));
table.Cell().Border(1).Padding(3).AlignRight().Text(item.PromedioTirada.ToString("N0"));
table.Cell().Border(1).Padding(3).AlignRight().Text(item.PromedioSinCargo.ToString("N0"));
table.Cell().Border(1).Padding(3).AlignRight().Text(item.PromedioPerdidos.ToString("N0"));
table.Cell().Border(1).Padding(3).AlignRight().Text(item.PromedioLlevados.ToString("N0"));
table.Cell().Border(1).Padding(3).AlignRight().Text(item.PromedioDevueltos.ToString("N0"));
table.Cell().Border(1).Padding(3).AlignRight().Text(item.PromedioVendidos.ToString("N0"));
}
// --- FILA GENERAL CON DATOS CALCULADOS DEL VIEWMODEL ---
var general = Model.PromedioGeneral;
if (general != null)
{
var boldStyle = TextStyle.Default.SemiBold();
table.Cell().Border(1).Padding(3).Text(text => text.Span(general.Dia).Style(boldStyle));
table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(general.CantidadDias.ToString("N0")).Style(boldStyle));
table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(general.PromedioTirada.ToString("N0")).Style(boldStyle));
table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(general.PromedioSinCargo.ToString("N0")).Style(boldStyle));
table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(general.PromedioPerdidos.ToString("N0")).Style(boldStyle));
table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(general.PromedioLlevados.ToString("N0")).Style(boldStyle));
table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(general.PromedioDevueltos.ToString("N0")).Style(boldStyle));
table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(general.PromedioVendidos.ToString("N0")).Style(boldStyle));
}
});
});
}
}
}

View File

@@ -0,0 +1,122 @@
using GestionIntegral.Api.Dtos.Reportes.ViewModels;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using System.Linq;
namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
{
public class MovimientoBobinasDocument : IDocument
{
public MovimientoBobinasViewModel Model { get; }
public MovimientoBobinasDocument(MovimientoBobinasViewModel model)
{
Model = model;
}
public DocumentMetadata GetMetadata() => DocumentMetadata.Default;
public DocumentSettings GetSettings() => DocumentSettings.Default;
public void Compose(IDocumentContainer container)
{
container
.Page(page =>
{
// Configuramos la página en modo apaisado (landscape)
page.Size(PageSizes.A4.Landscape());
page.Margin(1, Unit.Centimetre);
page.DefaultTextStyle(x => x.FontFamily("Roboto").FontSize(9)); // Un poco más pequeño por la cantidad de columnas
page.Header().Element(ComposeHeader);
page.Content().Element(ComposeContent);
page.Footer()
.AlignCenter()
.Text(x =>
{
x.Span("Página ");
x.CurrentPageNumber();
});
});
}
void ComposeHeader(IContainer container)
{
container.Column(column =>
{
column.Spacing(5);
column.Item().AlignCenter().Text("Reporte de Movimiento de Bobinas").SemiBold().FontSize(14);
column.Item().AlignCenter().Text($"Planta: {Model.NombrePlanta}").FontSize(12);
column.Item().PaddingTop(2, Unit.Millimetre).Row(row =>
{
row.RelativeItem().Column(col =>
{
col.Item().Text(text =>
{
text.Span("Fecha del Reporte: ").SemiBold();
text.Span(Model.FechaReporte);
});
col.Item().Text($"Periodo Consultado: Desde {Model.FechaDesde} Hasta {Model.FechaHasta}");
});
});
});
}
void ComposeContent(IContainer container)
{
container.PaddingTop(1, Unit.Centimetre).Table(table =>
{
// Definimos 11 columnas. Usamos una combinación de relativas y constantes
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn(2.5f); // Tipo
columns.ConstantColumn(50); // Cant. Inicial
columns.ConstantColumn(60); // Kg Iniciales
columns.ConstantColumn(50); // Compradas
columns.ConstantColumn(60); // Kg Comprados
columns.ConstantColumn(50); // Consumidas
columns.ConstantColumn(60); // Kg Consumidos
columns.ConstantColumn(50); // Dañadas
columns.ConstantColumn(60); // Kg Dañados
columns.ConstantColumn(50); // Cant. Final
columns.ConstantColumn(60); // Kg Final
});
// Encabezado de la tabla
table.Header(header =>
{
// Celda por celda para control total
header.Cell().Background(Colors.Grey.Lighten3).Padding(2).Text("Tipo");
header.Cell().Background(Colors.Grey.Lighten3).Padding(2).AlignCenter().Text("Cant. Inicial");
header.Cell().Background(Colors.Grey.Lighten3).Padding(2).AlignCenter().Text("Kg Inicial");
header.Cell().Background(Colors.Grey.Lighten3).Padding(2).AlignCenter().Text("Compradas");
header.Cell().Background(Colors.Grey.Lighten3).Padding(2).AlignCenter().Text("Kg Comprados");
header.Cell().Background(Colors.Grey.Lighten3).Padding(2).AlignCenter().Text("Consumidas");
header.Cell().Background(Colors.Grey.Lighten3).Padding(2).AlignCenter().Text("Kg Consumidos");
header.Cell().Background(Colors.Grey.Lighten3).Padding(2).AlignCenter().Text("Dañadas");
header.Cell().Background(Colors.Grey.Lighten3).Padding(2).AlignCenter().Text("Kg Dañados");
header.Cell().Background(Colors.Grey.Lighten3).Padding(2).AlignCenter().Text("Cant. Final");
header.Cell().Background(Colors.Grey.Lighten3).Padding(2).AlignCenter().Text("Kg Final");
});
// Filas de datos
foreach (var item in Model.Movimientos)
{
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(2).Text(item.TipoBobina);
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(2).AlignCenter().Text(item.BobinasIniciales.ToString("N0"));
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(2).AlignCenter().Text(item.KilosIniciales.ToString("N0"));
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(2).AlignCenter().Text(item.BobinasCompradas.ToString("N0"));
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(2).AlignCenter().Text(item.KilosComprados.ToString("N0"));
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(2).AlignCenter().Text(item.BobinasConsumidas.ToString("N0"));
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(2).AlignCenter().Text(item.KilosConsumidos.ToString("N0"));
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(2).AlignCenter().Text(item.BobinasDaniadas.ToString("N0"));
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(2).AlignCenter().Text(item.KilosDaniados.ToString("N0"));
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(2).AlignCenter().Text(item.BobinasFinales.ToString("N0"));
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(2).AlignCenter().Text(item.KilosFinales.ToString("N0"));
}
});
}
}
}

View File

@@ -0,0 +1,129 @@
using GestionIntegral.Api.Dtos.Reportes.ViewModels;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
{
public class MovimientoBobinasEstadoDocument : IDocument
{
public MovimientoBobinasEstadoViewModel Model { get; }
public MovimientoBobinasEstadoDocument(MovimientoBobinasEstadoViewModel model)
{
Model = model;
}
public DocumentMetadata GetMetadata() => DocumentMetadata.Default;
public DocumentSettings GetSettings() => DocumentSettings.Default;
public void Compose(IDocumentContainer container)
{
container
.Page(page =>
{
page.Margin(1, Unit.Centimetre);
page.DefaultTextStyle(x => x.FontFamily("Roboto").FontSize(10));
page.Header().Element(ComposeHeader);
page.Content().Element(ComposeContent);
page.Footer()
.AlignCenter()
.Text(x =>
{
x.Span("Página ");
x.CurrentPageNumber();
});
});
}
void ComposeHeader(IContainer container)
{
container.Column(column =>
{
column.Spacing(5);
column.Item().AlignCenter().Text("Reporte de Movimiento de Bobinas por Estados").SemiBold().FontSize(14);
column.Item().AlignCenter().Text($"Planta: {Model.NombrePlanta}").FontSize(12);
column.Item().PaddingTop(2, Unit.Millimetre).Row(row =>
{
row.RelativeItem().Column(col =>
{
col.Item().Text(text =>
{
text.Span("Fecha del Reporte: ").SemiBold();
text.Span(Model.FechaReporte);
});
col.Item().Text($"Periodo Consultado: Desde {Model.FechaDesde} Hasta {Model.FechaHasta}");
});
});
});
}
void ComposeContent(IContainer container)
{
container.PaddingTop(1, Unit.Centimetre).Column(column =>
{
// Primera tabla: Detalle de Movimientos
column.Item().Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn(3); // Tipo Bobina
columns.RelativeColumn(2); // Remito
columns.ConstantColumn(80); // Fecha
columns.ConstantColumn(60); // Cantidad
columns.RelativeColumn(2); // Tipo Movimiento
});
table.Header(header =>
{
header.Cell().Background(Colors.Grey.Lighten3).Padding(4).Text("Tipo Bobina");
header.Cell().Background(Colors.Grey.Lighten3).Padding(4).Text("N° Remito");
header.Cell().Background(Colors.Grey.Lighten3).Padding(4).AlignCenter().Text("Fecha Mov.");
header.Cell().Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Cantidad");
header.Cell().Background(Colors.Grey.Lighten3).Padding(4).Text("Tipo Movimiento");
});
foreach (var item in Model.Detalles)
{
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(4).Text(item.TipoBobina);
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(4).Text(item.NumeroRemito);
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(4).AlignCenter().Text(item.FechaMovimiento.ToString("dd/MM/yyyy"));
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(4).AlignRight().Text(item.Cantidad.ToString("N0"));
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(4).Text(item.TipoMovimiento);
}
});
// Espacio entre tablas
column.Item().PaddingTop(1, Unit.Centimetre);
// Segunda tabla: Totales
column.Item().AlignLeft().Table(table => // Alineamos la tabla a la izquierda para que no ocupe todo el ancho
{
table.ColumnsDefinition(columns =>
{
columns.ConstantColumn(120); // Tipo Movimiento
columns.ConstantColumn(80); // Total Bobinas
columns.ConstantColumn(80); // Total Kilos
});
table.Header(header =>
{
header.Cell().Background(Colors.Grey.Lighten3).Padding(4).Text("Totales por Movimiento");
header.Cell().Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Total Bobinas");
header.Cell().Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Total Kilos");
});
foreach (var total in Model.Totales)
{
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(4).Text(total.TipoMovimiento);
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(4).AlignRight().Text(total.TotalBobinas.ToString("N0"));
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(4).AlignRight().Text(total.TotalKilos.ToString("N0"));
}
});
});
}
}
}

View File

@@ -0,0 +1,144 @@
using GestionIntegral.Api.Dtos.Reportes.ViewModels;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using System.Globalization;
using System.Linq;
namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
{
public class NovedadesCanillasDocument : IDocument
{
public NovedadesCanillasViewModel Model { get; }
private static readonly CultureInfo CultureAr = new CultureInfo("es-AR");
public NovedadesCanillasDocument(NovedadesCanillasViewModel model)
{
Model = model;
}
public DocumentMetadata GetMetadata() => DocumentMetadata.Default;
public void Compose(IDocumentContainer container)
{
container.Page(page =>
{
page.Margin(1.5f, Unit.Centimetre);
page.DefaultTextStyle(x => x.FontFamily("Arial").FontSize(10));
page.Header().Element(ComposeHeader);
page.Content().Element(ComposeContent);
page.Footer().AlignCenter().Text(x => { x.Span("Página "); x.CurrentPageNumber(); });
});
}
void ComposeHeader(IContainer container)
{
container.Column(column =>
{
column.Item().AlignCenter().Text("Listado de Novedades - Canillas").SemiBold().FontSize(16);
column.Item().PaddingTop(5, Unit.Millimetre).Row(row =>
{
row.RelativeItem().Text(text => { text.Span("Fecha de Reporte: ").SemiBold(); text.Span(Model.FechaReporte); });
row.RelativeItem().AlignRight().Text(text => { text.Span("Periodo: ").SemiBold(); text.Span($"{Model.FechaDesde} - {Model.FechaHasta}"); });
});
column.Item().PaddingTop(5).Border(1).Background(Colors.Grey.Lighten3).AlignCenter().Padding(2).Text(Model.NombreEmpresa).SemiBold();
});
}
void ComposeContent(IContainer container)
{
container.PaddingTop(1, Unit.Centimetre).Column(column =>
{
column.Spacing(20);
if (Model.ResumenCanillas.Any())
{
column.Item().Element(ComposeResumenTable);
}
if (Model.DetallesNovedades.Any())
{
column.Item().Element(ComposeDetallesNovedadesTable);
}
});
}
void ComposeResumenTable(IContainer container)
{
container.Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn(3); // Canilla
columns.RelativeColumn(1); // Legajo
columns.RelativeColumn(1); // Faltas
columns.RelativeColumn(1); // Francos
columns.RelativeColumn(1.5f); // Comisiones
});
table.Header(header =>
{
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).Text("Canilla");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).Text("Legajo");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignCenter().Text("Faltas");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignCenter().Text("Francos");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Comisiones");
});
foreach (var item in Model.ResumenCanillas.OrderBy(x => x.Canilla))
{
table.Cell().Border(1).Padding(3).Text(item.Canilla);
table.Cell().Border(1).Padding(3).Text(item.Legajo?.ToString() ?? "-");
table.Cell().Border(1).Padding(3).AlignCenter().Text(item.Faltas?.ToString("N0") ?? "0");
table.Cell().Border(1).Padding(3).AlignCenter().Text(item.Francos?.ToString("N0") ?? "0");
table.Cell().Border(1).Padding(3).AlignRight().Text(item.TotalRendir?.ToString("C", CultureAr) ?? "$ 0.00");
}
// Fila de Totales
table.Cell().ColumnSpan(4).Border(1).Padding(3).AlignRight().Text("Total").SemiBold();
table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(Model.ResumenCanillas.Sum(x => x.TotalRendir ?? 0).ToString("C", CultureAr)).SemiBold());
});
}
void ComposeDetallesNovedadesTable(IContainer container)
{
container.Column(column =>
{
column.Item().PaddingTop(10).Text("Otras Novedades").SemiBold().FontSize(12);
column.Item().PaddingTop(5).Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn(2); // Nombre
columns.ConstantColumn(80); // Fecha
columns.RelativeColumn(4); // Detalle
});
table.Header(header =>
{
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).Text("Nombre");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).Text("Fecha");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).Text("Detalle");
});
// Agrupamos por canillita para mostrar su nombre una sola vez
foreach (var grupo in Model.DetallesNovedades.GroupBy(x => x.NomApe))
{
// Celda con el nombre del canillita, abarcando todas las filas de sus novedades
table.Cell().RowSpan((uint)grupo.Count()).Border(1).Padding(3).Text(grupo.Key);
// Iteramos sobre las novedades del grupo
foreach (var detalle in grupo.OrderBy(d => d.Fecha))
{
table.Cell().Border(1).Padding(3).Text(detalle.Fecha.ToString("dd/MM/yyyy"));
table.Cell().Border(1).Padding(3).Text(detalle.Detalle);
}
}
});
});
}
}
}

View File

@@ -0,0 +1,99 @@
using GestionIntegral.Api.Dtos.Reportes.ViewModels;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using System.Linq;
namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
{
public class TiradasPublicacionesSeccionesDocument : IDocument
{
public TiradasPublicacionesSeccionesViewModel Model { get; }
public TiradasPublicacionesSeccionesDocument(TiradasPublicacionesSeccionesViewModel model)
{
Model = model;
}
public DocumentMetadata GetMetadata() => DocumentMetadata.Default;
public void Compose(IDocumentContainer container)
{
container.Page(page =>
{
page.Margin(1, Unit.Centimetre);
page.DefaultTextStyle(x => x.FontFamily("Arial").FontSize(9));
page.Header().Element(ComposeHeader);
page.Content().Element(ComposeContent);
page.Footer().AlignCenter().Text(x => { x.Span("Página "); x.CurrentPageNumber(); });
});
}
void ComposeHeader(IContainer container)
{
container.Column(column =>
{
column.Spacing(5);
column.Item().AlignCenter().Text("Reporte de Tiradas por Publicación Mensual").SemiBold().FontSize(14);
// Título secundario dinámico
string subTitle = Model.EsConsolidado
? $"Consolidado - Publicación: {Model.NombrePublicacion}"
: $"Planta: {Model.NombrePlanta} - Publicación: {Model.NombrePublicacion}";
column.Item().AlignCenter().Text(subTitle).FontSize(12);
column.Item().PaddingTop(5, Unit.Millimetre).Row(row =>
{
row.RelativeItem().Text(text => { text.Span("Fecha del Reporte: ").SemiBold(); text.Span(Model.FechaReporte); });
row.RelativeItem().AlignRight().Text(text => { text.Span("Mes Consultado: ").SemiBold(); text.Span(Model.MesConsultado); });
});
});
}
void ComposeContent(IContainer container)
{
container.PaddingTop(5, Unit.Millimetre).Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn(2.5f); // Nombre Seccion
columns.RelativeColumn(1.5f); // Páginas Impresas
columns.RelativeColumn(1); // Total Ediciones
columns.RelativeColumn(1.5f); // Pág. Por Edición
columns.RelativeColumn(1.2f); // Total Ejemplares
columns.RelativeColumn(1.5f); // Pág. Ejemplar
});
table.Header(header =>
{
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).Text("Nombre Sección");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Páginas Impresas");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Total Ediciones");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Prom. Pág/Edición");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Total Ejemplares");
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Prom. Pág/Ejemplar");
});
foreach (var item in Model.Detalles.OrderBy(x => x.NombreSeccion))
{
table.Cell().Border(1).Padding(3).Text(item.NombreSeccion);
table.Cell().Border(1).Padding(3).AlignRight().Text(item.TotalPaginasImpresas.ToString("N0"));
table.Cell().Border(1).Padding(3).AlignRight().Text(item.CantidadTiradas.ToString("N0"));
table.Cell().Border(1).Padding(3).AlignRight().Text(item.TotalPaginasEjemplares.ToString("N0"));
table.Cell().Border(1).Padding(3).AlignRight().Text(item.TotalEjemplares.ToString("N0"));
table.Cell().Border(1).Padding(3).AlignRight().Text(item.PromedioPaginasPorEjemplar.ToString("N0"));
}
// Fila de Totales
var style = TextStyle.Default.SemiBold();
table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span("Totales").Style(style));
table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(Model.Detalles.Sum(x => x.TotalPaginasImpresas).ToString("N0")).Style(style));
table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(Model.Detalles.Sum(x => x.CantidadTiradas).ToString("N0")).Style(style));
table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(Model.Detalles.Sum(x => x.TotalPaginasEjemplares).ToString("N0")).Style(style));
table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(Model.Detalles.Sum(x => x.TotalEjemplares).ToString("N0")).Style(style));
table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(Model.Detalles.Sum(x => x.PromedioPaginasPorEjemplar).ToString("N0")).Style(style));
});
}
}
}

View File

@@ -0,0 +1,89 @@
using GestionIntegral.Api.Dtos.Reportes.ViewModels;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using System.Linq;
namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
{
public class VentaMensualSecretariaElDiaDocument : IDocument
{
public VentaMensualSecretariaElDiaViewModel Model { get; }
public VentaMensualSecretariaElDiaDocument(VentaMensualSecretariaElDiaViewModel model)
{
Model = model;
}
public DocumentMetadata GetMetadata() => DocumentMetadata.Default;
public void Compose(IDocumentContainer container)
{
container.Page(page =>
{
page.Margin(1, Unit.Centimetre);
page.DefaultTextStyle(x => x.FontFamily("Arial").FontSize(11));
page.Header().Element(ComposeHeader);
page.Content().Element(ComposeContent);
page.Footer().AlignCenter().Text(x => { x.Span("Página "); x.CurrentPageNumber(); });
});
}
void ComposeHeader(IContainer container)
{
container.Column(column =>
{
column.Spacing(5);
column.Item().AlignCenter().Text("VENTA DIARIO EL DÍA").SemiBold().FontSize(16);
column.Item().AlignCenter().Text(text =>
{
text.Span("Fecha Consultada: Desde ").SemiBold().FontSize(12);
text.Span(Model.FechaDesde).FontSize(12);
text.Span(" Hasta ").SemiBold().FontSize(12);
text.Span(Model.FechaHasta).FontSize(12);
});
});
}
void ComposeContent(IContainer container)
{
container.PaddingTop(5, Unit.Millimetre).Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.ConstantColumn(50); // Día
columns.RelativeColumn(); // Canillas
columns.RelativeColumn(); // Tirajes
columns.RelativeColumn(); // Ventas
columns.RelativeColumn(); // Accionistas
columns.RelativeColumn(); // Total Coop.
columns.RelativeColumn(); // Total Gral.
});
table.Header(header =>
{
header.Cell().Background(Colors.Black).Border(1).BorderColor(Colors.White).Padding(4).AlignCenter().Text(text => text.Span("DÍA").FontColor(Colors.White).SemiBold());
header.Cell().Background(Colors.Black).Border(1).BorderColor(Colors.White).Padding(4).AlignCenter().Text(text => text.Span("CANILLAS").FontColor(Colors.White).SemiBold());
header.Cell().Background(Colors.Black).Border(1).BorderColor(Colors.White).Padding(4).AlignCenter().Text(text => text.Span("TIRAJES").FontColor(Colors.White).SemiBold());
header.Cell().Background(Colors.Black).Border(1).BorderColor(Colors.White).Padding(4).AlignCenter().Text(text => text.Span("VENTAS").FontColor(Colors.White).SemiBold());
header.Cell().Background(Colors.Black).Border(1).BorderColor(Colors.White).Padding(4).AlignCenter().Text(text => text.Span("ACCIONISTAS").FontColor(Colors.White).SemiBold());
header.Cell().Background(Colors.Black).Border(1).BorderColor(Colors.White).Padding(4).AlignCenter().Text(text => text.Span("TOTAL COOPERATIVA").FontColor(Colors.White).SemiBold());
header.Cell().Background(Colors.Black).Border(1).BorderColor(Colors.White).Padding(4).AlignCenter().Text(text => text.Span("TOTAL").FontColor(Colors.White).SemiBold());
});
foreach (var item in Model.VentasDiarias.OrderBy(x => x.Dia))
{
table.Cell().Border(1).Padding(4).AlignCenter().Text(item.Dia.ToString());
table.Cell().Border(1).Padding(4).AlignCenter().Text(item.CantidadCanillas.ToString("N0"));
table.Cell().Border(1).Padding(4).AlignCenter().Text(item.Tirajes.ToString("N0"));
table.Cell().Border(1).Padding(4).AlignCenter().Text(item.Ventas.ToString("N0"));
table.Cell().Border(1).Padding(4).AlignCenter().Text(item.Accionistas.ToString("N0"));
table.Cell().Border(1).Padding(4).AlignCenter().Text(item.TotalCooperativa.ToString("N0"));
table.Cell().Border(1).Padding(4).AlignCenter().Text(t => t.Span(item.TotalGeneral.ToString("N0")).SemiBold());
}
});
}
}
}

View File

@@ -0,0 +1,89 @@
using GestionIntegral.Api.Dtos.Reportes.ViewModels;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using System.Linq;
namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
{
public class VentaMensualSecretariaElPlataDocument : IDocument
{
public VentaMensualSecretariaElPlataViewModel Model { get; }
public VentaMensualSecretariaElPlataDocument(VentaMensualSecretariaElPlataViewModel model)
{
Model = model;
}
public DocumentMetadata GetMetadata() => DocumentMetadata.Default;
public void Compose(IDocumentContainer container)
{
container.Page(page =>
{
page.Margin(1, Unit.Centimetre);
page.DefaultTextStyle(x => x.FontFamily("Arial").FontSize(11));
page.Header().Element(ComposeHeader);
page.Content().Element(ComposeContent);
page.Footer().AlignCenter().Text(x => { x.Span("Página "); x.CurrentPageNumber(); });
});
}
void ComposeHeader(IContainer container)
{
container.Column(column =>
{
column.Spacing(5);
column.Item().AlignCenter().Text("VENTA DIARIO EL PLATA").SemiBold().FontSize(16);
column.Item().AlignCenter().Text(text =>
{
text.Span("Fecha Consultada: Desde ").SemiBold().FontSize(12);
text.Span(Model.FechaDesde).FontSize(12);
text.Span(" Hasta ").SemiBold().FontSize(12);
text.Span(Model.FechaHasta).FontSize(12);
});
});
}
void ComposeContent(IContainer container)
{
container.PaddingTop(5, Unit.Millimetre).Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.ConstantColumn(50); // Día
columns.RelativeColumn(); // Tirada Coop
columns.RelativeColumn(); // Devolución Coop
columns.RelativeColumn(); // Venta Coop
columns.RelativeColumn(); // Tirada Canillas
columns.RelativeColumn(); // Venta Canillas
columns.RelativeColumn(); // Total
});
table.Header(header =>
{
header.Cell().Background(Colors.Black).Border(1).BorderColor(Colors.White).Padding(4).AlignCenter().Text(text => text.Span("DÍA").FontColor(Colors.White).SemiBold());
header.Cell().Background(Colors.Black).Border(1).BorderColor(Colors.White).Padding(4).AlignCenter().Text(text => text.Span("TIRADA COOP.").FontColor(Colors.White).SemiBold());
header.Cell().Background(Colors.Black).Border(1).BorderColor(Colors.White).Padding(4).AlignCenter().Text(text => text.Span("DEVOLUCIÓN COOP.").FontColor(Colors.White).SemiBold());
header.Cell().Background(Colors.Black).Border(1).BorderColor(Colors.White).Padding(4).AlignCenter().Text(text => text.Span("VENTA COOP.").FontColor(Colors.White).SemiBold());
header.Cell().Background(Colors.Black).Border(1).BorderColor(Colors.White).Padding(4).AlignCenter().Text(text => text.Span("TIRADA CANILLAS").FontColor(Colors.White).SemiBold());
header.Cell().Background(Colors.Black).Border(1).BorderColor(Colors.White).Padding(4).AlignCenter().Text(text => text.Span("VENTA CANILLAS (TOTAL)").FontColor(Colors.White).SemiBold());
header.Cell().Background(Colors.Black).Border(1).BorderColor(Colors.White).Padding(4).AlignCenter().Text(text => text.Span("TOTAL").FontColor(Colors.White).SemiBold());
});
foreach (var item in Model.VentasDiarias.OrderBy(x => x.Dia))
{
table.Cell().Border(1).Padding(4).AlignCenter().Text(item.Dia.ToString());
table.Cell().Border(1).Padding(4).AlignCenter().Text(item.TiradaCoop.ToString("N0"));
table.Cell().Border(1).Padding(4).AlignCenter().Text(item.DevolucionCoop.ToString("N0"));
table.Cell().Border(1).Padding(4).AlignCenter().Text(item.VentaCoop.ToString("N0"));
table.Cell().Border(1).Padding(4).AlignCenter().Text(item.TiradaCan.ToString("N0"));
table.Cell().Border(1).Padding(4).AlignCenter().Text(item.VentaCan.ToString("N0"));
table.Cell().Border(1).Padding(4).AlignCenter().Text(t => t.Span(item.Total.ToString("N0")).SemiBold());
}
});
}
}
}

View File

@@ -0,0 +1,116 @@
using GestionIntegral.Api.Dtos.Reportes.ViewModels;
using QuestPDF.Elements.Table;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using System.Linq;
namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
{
public class VentaMensualSecretariaTirDevoDocument : IDocument
{
public VentaMensualSecretariaTirDevoViewModel Model { get; }
public VentaMensualSecretariaTirDevoDocument(VentaMensualSecretariaTirDevoViewModel model)
{
Model = model;
}
public DocumentMetadata GetMetadata() => DocumentMetadata.Default;
public void Compose(IDocumentContainer container)
{
container.Page(page =>
{
// CORRECCIÓN: Se aplica Landscape() al tamaño de página.
page.Size(PageSizes.A4.Landscape());
page.Margin(1, Unit.Centimetre);
page.DefaultTextStyle(x => x.FontFamily("Arial").FontSize(12));
page.Header().Element(ComposeHeader);
page.Content().Element(ComposeContent);
page.Footer().AlignCenter().Text(x => { x.Span("Página "); x.CurrentPageNumber(); });
});
}
void ComposeHeader(IContainer container)
{
container.Column(column =>
{
column.Item().AlignCenter().Text("TIRADA Y DEVOLUCIÓN").SemiBold().FontSize(18);
column.Item().AlignCenter().Text(text =>
{
text.Span("Fecha Consultada: Desde ").SemiBold().FontSize(14);
text.Span(Model.FechaDesde).FontSize(14);
text.Span(" Hasta ").SemiBold().FontSize(14);
text.Span(Model.FechaHasta).FontSize(14);
});
});
}
void ComposeContent(IContainer container)
{
container.PaddingTop(5, Unit.Millimetre).Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.ConstantColumn(40);
columns.RelativeColumn(); columns.RelativeColumn(); columns.RelativeColumn(); // EL DÍA
columns.RelativeColumn(); columns.RelativeColumn(); columns.RelativeColumn(); // POPULAR
columns.RelativeColumn(); columns.RelativeColumn(); columns.RelativeColumn(); // CLARÍN
columns.RelativeColumn(); columns.RelativeColumn(); columns.RelativeColumn(); // LA NACIÓN
});
table.Header(header =>
{
// CORRECCIÓN: La sintaxis de VerticalAlign es un método.
header.Cell().RowSpan(2).Border(1).Background(Colors.Black).AlignCenter().AlignMiddle().Text(text => text.Span("Día").FontColor(Colors.White).SemiBold());
header.Cell().ColumnSpan(3).Border(1).Background(Colors.Black).AlignCenter().Text(text => text.Span("EL DÍA").FontColor(Colors.White).SemiBold());
header.Cell().ColumnSpan(3).Border(1).Background(Colors.Black).AlignCenter().Text(text => text.Span("POPULAR").FontColor(Colors.White).SemiBold());
header.Cell().ColumnSpan(3).Border(1).Background(Colors.Black).AlignCenter().Text(text => text.Span("CLARÍN").FontColor(Colors.White).SemiBold());
header.Cell().ColumnSpan(3).Border(1).Background(Colors.Black).AlignCenter().Text(text => text.Span("LA NACIÓN").FontColor(Colors.White).SemiBold());
// CORRECCIÓN: Se define una función local para crear y estilizar las celdas del sub-encabezado.
// Esto evita el error de "multiple child elements".
void SubHeaderCell(ITableCellContainer cell, string text)
{
cell.Border(1)
.Background(Colors.Black)
.AlignCenter()
.Text(txt => txt.Span(text).FontColor(Colors.White).SemiBold());
}
foreach (var _ in Enumerable.Range(0, 4))
{
SubHeaderCell(header.Cell(), "TIRADA");
SubHeaderCell(header.Cell(), "DEVOLUC");
SubHeaderCell(header.Cell(), "VENTA");
}
});
// Filas de datos (sin cambios)
foreach (var item in Model.VentasDiarias.OrderBy(x => x.Dia))
{
table.Cell().Border(1).Padding(4).AlignCenter().Text(item.Dia.ToString());
table.Cell().Border(1).Padding(4).AlignCenter().Text(item.TiradaCoop.ToString("N0"));
table.Cell().Border(1).Padding(4).AlignCenter().Text(item.DevolucionCoop.ToString("N0"));
table.Cell().Border(1).Padding(4).AlignCenter().Text(t => t.Span(item.VentaCoop.ToString("N0")).SemiBold());
table.Cell().Border(1).Padding(4).AlignCenter().Text(item.TiradaPopular.ToString("N0"));
table.Cell().Border(1).Padding(4).AlignCenter().Text(item.DevolucionPopular.ToString("N0"));
table.Cell().Border(1).Padding(4).AlignCenter().Text(t => t.Span(item.VentaPopular.ToString("N0")).SemiBold());
table.Cell().Border(1).Padding(4).AlignCenter().Text(item.TiradaClarin.ToString("N0"));
table.Cell().Border(1).Padding(4).AlignCenter().Text(item.DevolucionClarin.ToString("N0"));
table.Cell().Border(1).Padding(4).AlignCenter().Text(t => t.Span(item.VentaClarin.ToString("N0")).SemiBold());
table.Cell().Border(1).Padding(4).AlignCenter().Text(item.TiradaNacion.ToString("N0"));
table.Cell().Border(1).Padding(4).AlignCenter().Text(item.DevolucionNacion.ToString("N0"));
table.Cell().Border(1).Padding(4).AlignCenter().Text(t => t.Span(item.VentaNacion.ToString("N0")).SemiBold());
}
});
}
}
}

View File

@@ -1,779 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Report xmlns="http://schemas.microsoft.com/sqlserver/reporting/2016/01/reportdefinition" xmlns:rd="http://schemas.microsoft.com/SQLServer/reporting/reportdesigner">
<AutoRefresh>0</AutoRefresh>
<DataSources>
<DataSource Name="DSConsumoBobinas">
<ConnectionProperties>
<DataProvider>System.Data.DataSet</DataProvider>
<ConnectString>/* Local Connection */</ConnectString>
</ConnectionProperties>
<rd:DataSourceID>2bf94014-b80d-4047-9164-c0b5ad337361</rd:DataSourceID>
</DataSource>
</DataSources>
<DataSets>
<DataSet Name="DSConsumoBobinasPublicacion">
<Query>
<DataSourceName>DSConsumoBobinas</DataSourceName>
<CommandText>/* Local Query */</CommandText>
</Query>
<Fields>
<Field Name="NombrePlanta">
<DataField>NombrePlanta</DataField>
<rd:TypeName>System.String</rd:TypeName>
</Field>
<Field Name="NombrePublicacion">
<DataField>NombrePublicacion</DataField>
<rd:TypeName>System.String</rd:TypeName>
</Field>
<Field Name="TotalKilos">
<DataField>TotalKilos</DataField>
<rd:TypeName>System.Int32</rd:TypeName>
</Field>
<Field Name="CantidadBobinas">
<DataField>CantidadBobinas</DataField>
<rd:TypeName>System.Int32</rd:TypeName>
</Field>
</Fields>
<rd:DataSetInfo>
<rd:DataSetName>DSConsumoBobinas</rd:DataSetName>
<rd:SchemaPath>C:\Users\dmolinari\source\repos\Cobol-VBNet\Reportes\DSConsumoBobinas.xsd</rd:SchemaPath>
<rd:TableName>SP_BobinasUtilizadasPorPublicacion</rd:TableName>
<rd:TableAdapterFillMethod>Fill</rd:TableAdapterFillMethod>
<rd:TableAdapterGetDataMethod>GetData</rd:TableAdapterGetDataMethod>
<rd:TableAdapterName>SP_BobinasUtilizadasPorPublicacionTableAdapter</rd:TableAdapterName>
</rd:DataSetInfo>
</DataSet>
</DataSets>
<ReportSections>
<ReportSection>
<Body>
<ReportItems>
<Tablix Name="Tablix1">
<TablixBody>
<TablixColumns>
<TablixColumn>
<Width>6cm</Width>
</TablixColumn>
<TablixColumn>
<Width>5cm</Width>
</TablixColumn>
<TablixColumn>
<Width>4cm</Width>
</TablixColumn>
</TablixColumns>
<TablixRows>
<TablixRow>
<Height>0.6cm</Height>
<TablixCells>
<TablixCell>
<CellContents>
<Textbox Name="Textbox11">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>Publicación</Value>
<Style>
<FontFamily>roboto</FontFamily>
<FontSize>11pt</FontSize>
<FontWeight>Bold</FontWeight>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox11</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="Textbox13">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>Kilos</Value>
<Style>
<FontFamily>roboto</FontFamily>
<FontSize>11pt</FontSize>
<FontWeight>Bold</FontWeight>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox13</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="Textbox15">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>Cant.Bobinas</Value>
<Style>
<FontFamily>roboto</FontFamily>
<FontSize>11pt</FontSize>
<FontWeight>Bold</FontWeight>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox15</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
</TablixCells>
</TablixRow>
<TablixRow>
<Height>0.6cm</Height>
<TablixCells>
<TablixCell>
<CellContents>
<Textbox Name="NombrePublicacion">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Fields!NombrePublicacion.Value</Value>
<Style />
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>NombrePublicacion</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="TotalKilos">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Fields!TotalKilos.Value</Value>
<Style>
<Format>#,0</Format>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>TotalKilos</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="CantidadBobinas">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Fields!CantidadBobinas.Value</Value>
<Style>
<Format>#,0</Format>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>CantidadBobinas</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
</TablixCells>
</TablixRow>
<TablixRow>
<Height>0.6cm</Height>
<TablixCells>
<TablixCell>
<CellContents>
<Textbox Name="Textbox48">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>Totales </Value>
<Style>
<FontFamily>roboto</FontFamily>
<FontSize>11pt</FontSize>
<FontWeight>Bold</FontWeight>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Right</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox48</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="Textbox50">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Sum(Fields!TotalKilos.Value)</Value>
<Style>
<Format>#,0</Format>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox50</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="Textbox51">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Sum(Fields!CantidadBobinas.Value)</Value>
<Style>
<Format>#,0</Format>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox51</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
</TablixCells>
</TablixRow>
</TablixRows>
</TablixBody>
<TablixColumnHierarchy>
<TablixMembers>
<TablixMember />
<TablixMember />
<TablixMember />
</TablixMembers>
</TablixColumnHierarchy>
<TablixRowHierarchy>
<TablixMembers>
<TablixMember>
<TablixHeader>
<Size>4cm</Size>
<CellContents>
<Textbox Name="Textbox17">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>Planta</Value>
<Style>
<FontFamily>roboto</FontFamily>
<FontSize>11pt</FontSize>
<FontWeight>Bold</FontWeight>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox17</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixHeader>
<TablixMembers>
<TablixMember />
</TablixMembers>
<KeepWithGroup>After</KeepWithGroup>
</TablixMember>
<TablixMember>
<Group Name="NombrePlanta">
<GroupExpressions>
<GroupExpression>=Fields!NombrePlanta.Value</GroupExpression>
</GroupExpressions>
</Group>
<SortExpressions>
<SortExpression>
<Value>=Fields!NombrePlanta.Value</Value>
</SortExpression>
</SortExpressions>
<TablixHeader>
<Size>4cm</Size>
<CellContents>
<Textbox Name="NombrePlanta1">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Fields!NombrePlanta.Value</Value>
<Style />
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>NombrePlanta1</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixHeader>
<TablixMembers>
<TablixMember>
<Group Name="Detalles" />
<TablixMembers>
<TablixMember />
</TablixMembers>
</TablixMember>
<TablixMember>
<KeepWithGroup>Before</KeepWithGroup>
</TablixMember>
</TablixMembers>
</TablixMember>
</TablixMembers>
</TablixRowHierarchy>
<DataSetName>DSConsumoBobinasPublicacion</DataSetName>
<Top>0.14111cm</Top>
<Left>0.619cm</Left>
<Height>1.8cm</Height>
<Width>19cm</Width>
<Style>
<Border>
<Style>None</Style>
</Border>
</Style>
</Tablix>
</ReportItems>
<Height>0.80957in</Height>
<Style />
</Body>
<Width>21cm</Width>
<Page>
<PageHeader>
<Height>1.91974cm</Height>
<PrintOnFirstPage>true</PrintOnFirstPage>
<PrintOnLastPage>true</PrintOnLastPage>
<ReportItems>
<Textbox Name="Textbox41">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>Reporte de Consumo de Bobinas por Publicaciones</Value>
<Style>
<FontSize>14pt</FontSize>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Center</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox41</rd:DefaultName>
<Top>0.28998cm</Top>
<Left>0.619cm</Left>
<Height>0.72912cm</Height>
<Width>19cm</Width>
<Style>
<Border>
<Style>None</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
<Textbox Name="Textbox43">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>Fecha del Reporte</Value>
<Style>
<FontWeight>Bold</FontWeight>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Left</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox41</rd:DefaultName>
<Top>1.16692cm</Top>
<Left>0.619cm</Left>
<Height>0.64974cm</Height>
<Width>3.43896cm</Width>
<ZIndex>1</ZIndex>
<Style>
<Border>
<Style>None</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
<Textbox Name="Textbox49">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=CDate(Globals!ExecutionTime).ToString("dd/MM/yyyy")</Value>
<Style />
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox49</rd:DefaultName>
<Top>1.16692cm</Top>
<Left>4.00151cm</Left>
<Height>0.6cm</Height>
<Width>2.60584cm</Width>
<ZIndex>2</ZIndex>
<Style>
<Border>
<Style>None</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
<Textbox Name="Textbox71">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>Fecha Consultada: Desde</Value>
<Style>
<FontWeight>Bold</FontWeight>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Left</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox41</rd:DefaultName>
<Top>1.17052cm</Top>
<Left>6.64968cm</Left>
<Height>0.64974cm</Height>
<Width>4.5cm</Width>
<ZIndex>3</ZIndex>
<Style>
<Border>
<Style>None</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
<Textbox Name="Textbox72">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=CDate(Parameters!FechaDesde.Value).ToString("dd/MM/yyyy")</Value>
<Style />
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox49</rd:DefaultName>
<Top>1.2058cm</Top>
<Left>11.0676cm</Left>
<Height>0.6cm</Height>
<Width>2.52646cm</Width>
<ZIndex>4</ZIndex>
<Style>
<Border>
<Style>None</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
<Textbox Name="Textbox76">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>Hasta</Value>
<Style>
<FontWeight>Bold</FontWeight>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Left</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox41</rd:DefaultName>
<Top>1.17052cm</Top>
<Left>13.31184cm</Left>
<Height>0.64974cm</Height>
<Width>1.13709cm</Width>
<ZIndex>5</ZIndex>
<Style>
<Border>
<Style>None</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
<Textbox Name="Textbox77">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=CDate(Parameters!FechaHasta.Value).ToString("dd/MM/yyyy")</Value>
<Style />
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox49</rd:DefaultName>
<Top>1.17052cm</Top>
<Left>14.44892cm</Left>
<Height>0.6cm</Height>
<Width>2.52646cm</Width>
<ZIndex>6</ZIndex>
<Style>
<Border>
<Style>None</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</ReportItems>
<Style>
<Border>
<Style>None</Style>
</Border>
</Style>
</PageHeader>
<PageHeight>29.7cm</PageHeight>
<PageWidth>21cm</PageWidth>
<LeftMargin>0cm</LeftMargin>
<RightMargin>0cm</RightMargin>
<TopMargin>0.5cm</TopMargin>
<BottomMargin>0.5cm</BottomMargin>
<ColumnSpacing>0.13cm</ColumnSpacing>
<Style />
</Page>
</ReportSection>
</ReportSections>
<ReportParameters>
<ReportParameter Name="FechaDesde">
<DataType>DateTime</DataType>
<Prompt>ReportParameter1</Prompt>
</ReportParameter>
<ReportParameter Name="FechaHasta">
<DataType>DateTime</DataType>
<Prompt>ReportParameter1</Prompt>
</ReportParameter>
</ReportParameters>
<ReportParametersLayout>
<GridLayoutDefinition>
<NumberOfColumns>4</NumberOfColumns>
<NumberOfRows>2</NumberOfRows>
<CellDefinitions>
<CellDefinition>
<ColumnIndex>0</ColumnIndex>
<RowIndex>0</RowIndex>
<ParameterName>FechaDesde</ParameterName>
</CellDefinition>
<CellDefinition>
<ColumnIndex>1</ColumnIndex>
<RowIndex>0</RowIndex>
<ParameterName>FechaHasta</ParameterName>
</CellDefinition>
</CellDefinitions>
</GridLayoutDefinition>
</ReportParametersLayout>
<rd:ReportUnitType>Cm</rd:ReportUnitType>
<rd:ReportID>4e743a44-a248-48fe-9b28-03d7da14e6e5</rd:ReportID>
</Report>

View File

@@ -1,992 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Report xmlns="http://schemas.microsoft.com/sqlserver/reporting/2016/01/reportdefinition" xmlns:rd="http://schemas.microsoft.com/SQLServer/reporting/reportdesigner">
<AutoRefresh>0</AutoRefresh>
<DataSources>
<DataSource Name="DSLiquidacionCanillas">
<ConnectionProperties>
<DataProvider>System.Data.DataSet</DataProvider>
<ConnectString>/* Local Connection */</ConnectString>
</ConnectionProperties>
<rd:DataSourceID>ee328c50-edc3-4726-92dc-19ec72ac5a56</rd:DataSourceID>
</DataSource>
</DataSources>
<DataSets>
<DataSet Name="DSLiquidacionCanillas">
<Query>
<DataSourceName>DSLiquidacionCanillas</DataSourceName>
<CommandText>/* Local Query */</CommandText>
</Query>
<Fields>
<Field Name="Publicacion">
<DataField>Publicacion</DataField>
<rd:TypeName>System.String</rd:TypeName>
</Field>
<Field Name="Canilla">
<DataField>Canilla</DataField>
<rd:TypeName>System.String</rd:TypeName>
</Field>
<Field Name="TotalCantSalida">
<DataField>TotalCantSalida</DataField>
<rd:TypeName>System.Int32</rd:TypeName>
</Field>
<Field Name="TotalCantEntrada">
<DataField>TotalCantEntrada</DataField>
<rd:TypeName>System.Int32</rd:TypeName>
</Field>
<Field Name="TotalRendir">
<DataField>TotalRendir</DataField>
<rd:TypeName>System.Decimal</rd:TypeName>
</Field>
<Field Name="PrecioEjemplar">
<DataField>PrecioEjemplar</DataField>
<rd:TypeName>System.Decimal</rd:TypeName>
</Field>
</Fields>
<rd:DataSetInfo>
<rd:DataSetName>DSLiquidacionCanillas</rd:DataSetName>
<rd:SchemaPath>C:\Users\dmolinari\source\repos\Cobol-VBNet\Reportes\DSLiquidacionCanillas.xsd</rd:SchemaPath>
<rd:TableName>SP_DistCanillasLiquidacion</rd:TableName>
<rd:TableAdapterFillMethod>Fill</rd:TableAdapterFillMethod>
<rd:TableAdapterGetDataMethod>GetData</rd:TableAdapterGetDataMethod>
<rd:TableAdapterName>SP_DistCanillasLiquidacionTableAdapter</rd:TableAdapterName>
</rd:DataSetInfo>
</DataSet>
</DataSets>
<ReportSections>
<ReportSection>
<Body>
<ReportItems>
<Tablix Name="Tablix1">
<TablixBody>
<TablixColumns>
<TablixColumn>
<Width>3.53124cm</Width>
</TablixColumn>
<TablixColumn>
<Width>3.10854cm</Width>
</TablixColumn>
<TablixColumn>
<Width>3.55834cm</Width>
</TablixColumn>
</TablixColumns>
<TablixRows>
<TablixRow>
<Height>0.5cm</Height>
<TablixCells>
<TablixCell>
<CellContents>
<Textbox Name="Textbox38">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>="Vendedor: " &amp; First(Fields!Canilla.Value, "DSLiquidacionCanillas")</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontSize>8pt</FontSize>
<FontWeight>Bold</FontWeight>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox38</rd:DefaultName>
<Style>
<Border>
<Style>None</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
<ColSpan>3</ColSpan>
</CellContents>
</TablixCell>
<TablixCell />
<TablixCell />
</TablixCells>
</TablixRow>
<TablixRow>
<Height>0.5cm</Height>
<TablixCells>
<TablixCell>
<CellContents>
<Textbox Name="Publicacion">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Fields!Publicacion.Value</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontSize>8pt</FontSize>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Publicacion</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="Textbox13">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>Retirados</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontSize>8pt</FontSize>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox13</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="TotalCantSalida">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Fields!TotalCantSalida.Value</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontSize>8pt</FontSize>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>TotalCantSalida</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
</TablixCells>
</TablixRow>
<TablixRow>
<Height>0.5cm</Height>
<TablixCells>
<TablixCell>
<CellContents>
<Textbox Name="Textbox7">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value />
<Style>
<FontSize>8pt</FontSize>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox7</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<TopBorder>
<Style>None</Style>
</TopBorder>
<BottomBorder>
<Style>None</Style>
</BottomBorder>
<LeftBorder>
<Style>None</Style>
</LeftBorder>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="Textbox14">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>Devueltos</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontSize>8pt</FontSize>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox14</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="Textbox12">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Fields!TotalCantEntrada.Value</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontSize>8pt</FontSize>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox8</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
</TablixCells>
</TablixRow>
<TablixRow>
<Height>0.5cm</Height>
<TablixCells>
<TablixCell>
<CellContents>
<Textbox Name="Textbox27">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value />
<Style>
<FontSize>8pt</FontSize>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox27</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<TopBorder>
<Style>None</Style>
</TopBorder>
<BottomBorder>
<Style>None</Style>
</BottomBorder>
<LeftBorder>
<Style>None</Style>
</LeftBorder>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="Textbox28">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>Vendidos</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontSize>8pt</FontSize>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox28</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="Textbox29">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Fields!TotalCantSalida.Value-Fields!TotalCantEntrada.Value</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontSize>8pt</FontSize>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox29</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
</TablixCells>
</TablixRow>
<TablixRow>
<Height>0.5cm</Height>
<TablixCells>
<TablixCell>
<CellContents>
<Textbox Name="Textbox33">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value />
<Style>
<FontSize>8pt</FontSize>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox33</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<TopBorder>
<Style>None</Style>
</TopBorder>
<BottomBorder>
<Style>None</Style>
</BottomBorder>
<LeftBorder>
<Style>None</Style>
</LeftBorder>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="Textbox30">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>Precio Unitario</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontSize>8pt</FontSize>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox30</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="Textbox32">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Fields!PrecioEjemplar.Value</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontSize>8pt</FontSize>
<Format>'$'#,0.00;'$'-#,0.00</Format>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox32</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
<rd:FormatSymbolCulture>es-AR</rd:FormatSymbolCulture>
</Style>
</Textbox>
</CellContents>
</TablixCell>
</TablixCells>
</TablixRow>
<TablixRow>
<Height>0.5cm</Height>
<TablixCells>
<TablixCell>
<CellContents>
<Textbox Name="Textbox35">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value />
<Style />
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox35</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>None</Style>
</Border>
<TopBorder>
<Style>None</Style>
</TopBorder>
<BottomBorder>
<Style>None</Style>
</BottomBorder>
<LeftBorder>
<Style>None</Style>
</LeftBorder>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="Textbox36">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>Importe Vendido</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontSize>8pt</FontSize>
<FontWeight>Bold</FontWeight>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox36</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<TopBorder>
<Color>Black</Color>
<Width>2pt</Width>
</TopBorder>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="Textbox37">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Fields!TotalRendir.Value</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontSize>8pt</FontSize>
<FontWeight>Bold</FontWeight>
<Format>'$'#,0.00;'$'-#,0.00</Format>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox37</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<TopBorder>
<Color>Black</Color>
<Width>2pt</Width>
</TopBorder>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
<rd:FormatSymbolCulture>es-AR</rd:FormatSymbolCulture>
</Style>
</Textbox>
</CellContents>
</TablixCell>
</TablixCells>
</TablixRow>
<TablixRow>
<Height>0.5cm</Height>
<TablixCells>
<TablixCell>
<CellContents>
<Textbox Name="Textbox1">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value />
<Style />
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox1</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>None</Style>
</Border>
<TopBorder>
<Style>None</Style>
</TopBorder>
<BottomBorder>
<Style>None</Style>
</BottomBorder>
<LeftBorder>
<Style>None</Style>
</LeftBorder>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="Textbox2">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>Total A Rendir</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontSize>8pt</FontSize>
<FontWeight>Bold</FontWeight>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox2</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<TopBorder>
<Color>Black</Color>
<Width>2pt</Width>
</TopBorder>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="Textbox4">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Sum(Fields!TotalRendir.Value, "DSLiquidacionCanillas")</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontSize>8pt</FontSize>
<FontWeight>Bold</FontWeight>
<Format>'$'#,0.00;'$'-#,0.00</Format>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox4</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<TopBorder>
<Color>Black</Color>
<Width>2pt</Width>
</TopBorder>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
<rd:FormatSymbolCulture>es-AR</rd:FormatSymbolCulture>
</Style>
</Textbox>
</CellContents>
</TablixCell>
</TablixCells>
</TablixRow>
</TablixRows>
</TablixBody>
<TablixColumnHierarchy>
<TablixMembers>
<TablixMember />
<TablixMember />
<TablixMember />
</TablixMembers>
</TablixColumnHierarchy>
<TablixRowHierarchy>
<TablixMembers>
<TablixMember>
<KeepWithGroup>After</KeepWithGroup>
</TablixMember>
<TablixMember>
<Group Name="Publicacion">
<GroupExpressions>
<GroupExpression>=Fields!Publicacion.Value</GroupExpression>
</GroupExpressions>
</Group>
<SortExpressions>
<SortExpression>
<Value>=Fields!Publicacion.Value</Value>
</SortExpression>
</SortExpressions>
<TablixMembers>
<TablixMember />
<TablixMember />
<TablixMember />
<TablixMember />
<TablixMember />
</TablixMembers>
</TablixMember>
<TablixMember>
<KeepWithGroup>Before</KeepWithGroup>
</TablixMember>
</TablixMembers>
</TablixRowHierarchy>
<DataSetName>DSLiquidacionCanillas</DataSetName>
<Top>1.07592cm</Top>
<Left>2.09225cm</Left>
<Height>3.5cm</Height>
<Width>10.19813cm</Width>
<Style>
<Border>
<Style>None</Style>
</Border>
<FontSize>8pt</FontSize>
</Style>
</Tablix>
<Textbox Name="Textbox39">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>EL DIA S.A.I.C. y F.</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontSize>8pt</FontSize>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox39</rd:DefaultName>
<Top>0.01025cm</Top>
<Left>2.09225cm</Left>
<Height>0.5cm</Height>
<Width>10.19813cm</Width>
<ZIndex>1</ZIndex>
<Style>
<Border>
<Style>None</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
<Textbox Name="Textbox40">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>Liquidación venta de diarios del:</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontSize>8pt</FontSize>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Left</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox39</rd:DefaultName>
<Top>0.53236cm</Top>
<Left>2.09225cm</Left>
<Height>0.5cm</Height>
<Width>4.43198cm</Width>
<ZIndex>2</ZIndex>
<Style>
<Border>
<Style>None</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
<Textbox Name="Textbox41">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Parameters!FechaLiqui.Value</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontSize>8pt</FontSize>
<Format>dd/MM/yyy</Format>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Left</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox41</rd:DefaultName>
<Top>0.53236cm</Top>
<Left>6.44662cm</Left>
<Height>0.5cm</Height>
<Width>3.86292cm</Width>
<ZIndex>3</ZIndex>
<Style>
<Border>
<Style>None</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</ReportItems>
<Height>4.66667cm</Height>
<Style />
</Body>
<Width>14.8cm</Width>
<Page>
<PageHeight>21cm</PageHeight>
<PageWidth>14.8cm</PageWidth>
<LeftMargin>0cm</LeftMargin>
<RightMargin>0cm</RightMargin>
<TopMargin>0.5cm</TopMargin>
<BottomMargin>0.5cm</BottomMargin>
<ColumnSpacing>0.13cm</ColumnSpacing>
<Style />
</Page>
</ReportSection>
</ReportSections>
<ReportParameters>
<ReportParameter Name="FechaLiqui">
<DataType>DateTime</DataType>
<Prompt>ReportParameter1</Prompt>
</ReportParameter>
</ReportParameters>
<ReportParametersLayout>
<GridLayoutDefinition>
<NumberOfColumns>4</NumberOfColumns>
<NumberOfRows>2</NumberOfRows>
<CellDefinitions>
<CellDefinition>
<ColumnIndex>0</ColumnIndex>
<RowIndex>0</RowIndex>
<ParameterName>FechaLiqui</ParameterName>
</CellDefinition>
</CellDefinitions>
</GridLayoutDefinition>
</ReportParametersLayout>
<rd:ReportUnitType>Cm</rd:ReportUnitType>
<rd:ReportID>3bfdc2c9-c7dc-47b8-b2b1-3512fa773a78</rd:ReportID>
</Report>

View File

@@ -1,994 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Report xmlns="http://schemas.microsoft.com/sqlserver/reporting/2016/01/reportdefinition" xmlns:rd="http://schemas.microsoft.com/SQLServer/reporting/reportdesigner">
<AutoRefresh>0</AutoRefresh>
<DataSources>
<DataSource Name="DSConsumoBobinas">
<ConnectionProperties>
<DataProvider>System.Data.DataSet</DataProvider>
<ConnectString>/* Local Connection */</ConnectString>
</ConnectionProperties>
<rd:DataSourceID>7c4f9d40-0118-4f83-ae46-6a1b074be5d7</rd:DataSourceID>
</DataSource>
</DataSources>
<DataSets>
<DataSet Name="DSTiradasPublicacionesSecciones">
<Query>
<DataSourceName>DSConsumoBobinas</DataSourceName>
<CommandText>/* Local Query */</CommandText>
</Query>
<Fields>
<Field Name="NombreSeccion">
<DataField>NombreSeccion</DataField>
<rd:TypeName>System.String</rd:TypeName>
</Field>
<Field Name="TotalPaginasImpresas">
<DataField>TotalPaginasImpresas</DataField>
<rd:TypeName>System.Int32</rd:TypeName>
</Field>
<Field Name="CantidadTiradas">
<DataField>CantidadTiradas</DataField>
<rd:TypeName>System.Int32</rd:TypeName>
</Field>
<Field Name="TotalPaginasEjemplares">
<DataField>TotalPaginasEjemplares</DataField>
<rd:TypeName>System.Int32</rd:TypeName>
</Field>
<Field Name="TotalEjemplares">
<DataField>TotalEjemplares</DataField>
<rd:TypeName>System.Int32</rd:TypeName>
</Field>
<Field Name="PromedioPaginasPorEjemplar">
<DataField>PromedioPaginasPorEjemplar</DataField>
<rd:TypeName>System.Int32</rd:TypeName>
</Field>
</Fields>
<rd:DataSetInfo>
<rd:DataSetName>DSConsumoBobinas</rd:DataSetName>
<rd:SchemaPath>C:\Users\dmolinari\source\repos\Cobol-VBNet\Reportes\DSConsumoBobinas.xsd</rd:SchemaPath>
<rd:TableName>SP_TiradasPublicacionesSecciones</rd:TableName>
<rd:TableAdapterFillMethod>Fill</rd:TableAdapterFillMethod>
<rd:TableAdapterGetDataMethod>GetData</rd:TableAdapterGetDataMethod>
<rd:TableAdapterName>SP_TiradasPublicacionesSeccionesTableAdapter</rd:TableAdapterName>
</rd:DataSetInfo>
</DataSet>
</DataSets>
<ReportSections>
<ReportSection>
<Body>
<ReportItems>
<Tablix Name="Tablix1">
<TablixBody>
<TablixColumns>
<TablixColumn>
<Width>5.5cm</Width>
</TablixColumn>
<TablixColumn>
<Width>3cm</Width>
</TablixColumn>
<TablixColumn>
<Width>2.5cm</Width>
</TablixColumn>
<TablixColumn>
<Width>3cm</Width>
</TablixColumn>
<TablixColumn>
<Width>2.5cm</Width>
</TablixColumn>
<TablixColumn>
<Width>2.5cm</Width>
</TablixColumn>
</TablixColumns>
<TablixRows>
<TablixRow>
<Height>0.6cm</Height>
<TablixCells>
<TablixCell>
<CellContents>
<Textbox Name="Textbox9">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>Nombre </Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontSize>9pt</FontSize>
<FontWeight>Bold</FontWeight>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox9</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="Textbox11">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>Total Páginas Impresas</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontSize>9pt</FontSize>
<FontWeight>Bold</FontWeight>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox11</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="Textbox13">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>Total Ediciones</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontSize>9pt</FontSize>
<FontWeight>Bold</FontWeight>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox13</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="Textbox15">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>Pág. Por Edición (Promedio)</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontSize>9pt</FontSize>
<FontWeight>Bold</FontWeight>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox15</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="Textbox17">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>Total Ejemplares</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontSize>9pt</FontSize>
<FontWeight>Bold</FontWeight>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox17</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="Textbox19">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>Pág. Ejemplar (Promedio)</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontSize>9pt</FontSize>
<FontWeight>Bold</FontWeight>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox19</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
</TablixCells>
</TablixRow>
<TablixRow>
<Height>0.6cm</Height>
<TablixCells>
<TablixCell>
<CellContents>
<Textbox Name="NombreSeccion">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Fields!NombreSeccion.Value</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontSize>9pt</FontSize>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>NombreSeccion</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="TotalPaginasImpresas">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Fields!TotalPaginasImpresas.Value</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontSize>9pt</FontSize>
<Format>#,0;(#,0)</Format>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>TotalPaginasImpresas</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="CantidadTiradas">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Fields!CantidadTiradas.Value</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontSize>9pt</FontSize>
<Format>#,0;(#,0)</Format>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>CantidadTiradas</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="TotalPaginasEjemplares">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Fields!TotalPaginasEjemplares.Value</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontSize>9pt</FontSize>
<Format>#,0;(#,0)</Format>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>TotalPaginasEjemplares</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="TotalEjemplares">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Fields!TotalEjemplares.Value</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontSize>9pt</FontSize>
<Format>#,0;(#,0)</Format>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>TotalEjemplares</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="PromedioPaginasPorEjemplar">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Fields!PromedioPaginasPorEjemplar.Value</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontSize>9pt</FontSize>
<Format>#,0;(#,0)</Format>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>PromedioPaginasPorEjemplar</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
</TablixCells>
</TablixRow>
<TablixRow>
<Height>0.6cm</Height>
<TablixCells>
<TablixCell>
<CellContents>
<Textbox Name="Textbox21">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>Totales </Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontWeight>Bold</FontWeight>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Right</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox21</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="Textbox22">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Sum(Fields!TotalPaginasImpresas.Value, "DSTiradasPublicacionesSecciones")</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<Format>#,0;(#,0)</Format>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox22</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="Textbox23">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Sum(Fields!CantidadTiradas.Value, "DSTiradasPublicacionesSecciones")</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<Format>#,0;(#,0)</Format>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox23</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="Textbox24">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Sum(Fields!TotalPaginasEjemplares.Value, "DSTiradasPublicacionesSecciones")</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<Format>#,0;(#,0)</Format>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox24</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="Textbox25">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Sum(Fields!TotalEjemplares.Value, "DSTiradasPublicacionesSecciones")</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<Format>#,0;(#,0)</Format>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox25</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="Textbox26">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Sum(Fields!PromedioPaginasPorEjemplar.Value, "DSTiradasPublicacionesSecciones")</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<Format>#,0;(#,0)</Format>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox26</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
</TablixCells>
</TablixRow>
</TablixRows>
</TablixBody>
<TablixColumnHierarchy>
<TablixMembers>
<TablixMember />
<TablixMember />
<TablixMember />
<TablixMember />
<TablixMember />
<TablixMember />
</TablixMembers>
</TablixColumnHierarchy>
<TablixRowHierarchy>
<TablixMembers>
<TablixMember>
<KeepWithGroup>After</KeepWithGroup>
</TablixMember>
<TablixMember>
<Group Name="Detalles" />
</TablixMember>
<TablixMember>
<KeepWithGroup>Before</KeepWithGroup>
</TablixMember>
</TablixMembers>
</TablixRowHierarchy>
<DataSetName>DSTiradasPublicacionesSecciones</DataSetName>
<Top>0.14111cm</Top>
<Left>0.619cm</Left>
<Height>1.8cm</Height>
<Width>19cm</Width>
<Style>
<Border>
<Style>None</Style>
</Border>
</Style>
</Tablix>
</ReportItems>
<Height>0.84039in</Height>
<Style />
</Body>
<Width>21cm</Width>
<Page>
<PageHeader>
<Height>2.55474cm</Height>
<PrintOnFirstPage>true</PrintOnFirstPage>
<PrintOnLastPage>true</PrintOnLastPage>
<ReportItems>
<Textbox Name="Textbox41">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>Reporte de Tiradas por Publicación Mensual</Value>
<Style>
<FontSize>14pt</FontSize>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Center</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox41</rd:DefaultName>
<Top>0.27552cm</Top>
<Left>0.619cm</Left>
<Height>0.72912cm</Height>
<Width>19cm</Width>
<Style>
<Border>
<Style>None</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
<Textbox Name="Textbox42">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>="Consolidados - Publicación: " &amp; Parameters!NomPubli.Value</Value>
<Style>
<FontSize>12pt</FontSize>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Center</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox41</rd:DefaultName>
<Top>1.07428cm</Top>
<Left>0.619cm</Left>
<Height>0.64975cm</Height>
<Width>19cm</Width>
<ZIndex>1</ZIndex>
<Style>
<Border>
<Style>None</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
<Textbox Name="Textbox43">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>Fecha del Reporte</Value>
<Style>
<FontWeight>Bold</FontWeight>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Left</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox41</rd:DefaultName>
<Top>1.78746cm</Top>
<Left>0.619cm</Left>
<Height>0.64974cm</Height>
<Width>3.43896cm</Width>
<ZIndex>2</ZIndex>
<Style>
<Border>
<Style>None</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
<Textbox Name="Textbox49">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=CDate(Globals!ExecutionTime).ToString("dd/MM/yyyy")</Value>
<Style />
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox49</rd:DefaultName>
<Top>1.78746cm</Top>
<Left>4.00151cm</Left>
<Height>0.6cm</Height>
<Width>2.60584cm</Width>
<ZIndex>3</ZIndex>
<Style>
<Border>
<Style>None</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
<Textbox Name="Textbox71">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>Mes Consultado:</Value>
<Style>
<FontWeight>Bold</FontWeight>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Left</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox41</rd:DefaultName>
<Top>1.81998cm</Top>
<Left>6.60123cm</Left>
<Height>0.64974cm</Height>
<Width>3.25646cm</Width>
<ZIndex>4</ZIndex>
<Style>
<Border>
<Style>None</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
<Textbox Name="Textbox72">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Parameters!Mes.Value</Value>
<Style />
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox49</rd:DefaultName>
<Top>1.85526cm</Top>
<Left>9.74915cm</Left>
<Height>0.6cm</Height>
<Width>7.02438cm</Width>
<ZIndex>5</ZIndex>
<Style>
<Border>
<Style>None</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</ReportItems>
<Style>
<Border>
<Style>None</Style>
</Border>
</Style>
</PageHeader>
<PageHeight>29.7cm</PageHeight>
<PageWidth>21cm</PageWidth>
<LeftMargin>0cm</LeftMargin>
<RightMargin>0cm</RightMargin>
<TopMargin>0.5cm</TopMargin>
<BottomMargin>0.5cm</BottomMargin>
<ColumnSpacing>0.13cm</ColumnSpacing>
<Style />
</Page>
</ReportSection>
</ReportSections>
<ReportParameters>
<ReportParameter Name="Mes">
<DataType>String</DataType>
<Prompt>ReportParameter1</Prompt>
</ReportParameter>
<ReportParameter Name="NomPubli">
<DataType>String</DataType>
<Prompt>ReportParameter1</Prompt>
</ReportParameter>
</ReportParameters>
<ReportParametersLayout>
<GridLayoutDefinition>
<NumberOfColumns>4</NumberOfColumns>
<NumberOfRows>2</NumberOfRows>
<CellDefinitions>
<CellDefinition>
<ColumnIndex>0</ColumnIndex>
<RowIndex>0</RowIndex>
<ParameterName>Mes</ParameterName>
</CellDefinition>
<CellDefinition>
<ColumnIndex>2</ColumnIndex>
<RowIndex>0</RowIndex>
<ParameterName>NomPubli</ParameterName>
</CellDefinition>
</CellDefinitions>
</GridLayoutDefinition>
</ReportParametersLayout>
<rd:ReportUnitType>Cm</rd:ReportUnitType>
<rd:ReportID>41f590aa-e80a-45c7-8c64-d8fe8117f5ff</rd:ReportID>
</Report>

View File

@@ -1,879 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Report xmlns="http://schemas.microsoft.com/sqlserver/reporting/2016/01/reportdefinition" xmlns:rd="http://schemas.microsoft.com/SQLServer/reporting/reportdesigner">
<AutoRefresh>0</AutoRefresh>
<DataSources>
<DataSource Name="DSListadoDistribucion">
<ConnectionProperties>
<DataProvider>System.Data.DataSet</DataProvider>
<ConnectString>/* Local Connection */</ConnectString>
</ConnectionProperties>
<rd:DataSourceID>afdb4b97-a9ea-4bab-b77b-a7c9e72d6fac</rd:DataSourceID>
</DataSource>
</DataSources>
<DataSets>
<DataSet Name="DSListadoDistribucion">
<Query>
<DataSourceName>DSListadoDistribucion</DataSourceName>
<CommandText>/* Local Query */</CommandText>
</Query>
<Fields>
<Field Name="Dia">
<DataField>Dia</DataField>
<rd:TypeName>System.Int32</rd:TypeName>
</Field>
<Field Name="CantidadCanillas">
<DataField>CantidadCanillas</DataField>
<rd:TypeName>System.Int32</rd:TypeName>
</Field>
<Field Name="Tirajes">
<DataField>Tirajes</DataField>
<rd:TypeName>System.Int32</rd:TypeName>
</Field>
<Field Name="Ventas">
<DataField>Ventas</DataField>
<rd:TypeName>System.Int32</rd:TypeName>
</Field>
<Field Name="Accionistas">
<DataField>Accionistas</DataField>
<rd:TypeName>System.Int32</rd:TypeName>
</Field>
<Field Name="TotalCooperativa">
<DataField>TotalCooperativa</DataField>
<rd:TypeName>System.Int32</rd:TypeName>
</Field>
<Field Name="TotalGeneral">
<DataField>TotalGeneral</DataField>
<rd:TypeName>System.Int32</rd:TypeName>
</Field>
</Fields>
<rd:DataSetInfo>
<rd:DataSetName>DSListadoDistribucion</rd:DataSetName>
<rd:SchemaPath>C:\Users\dmolinari\source\repos\Cobol-VBNet\Reportes\DSListadoDistribucion.xsd</rd:SchemaPath>
<rd:TableName>SP_VentaMensualSecretariaElDia</rd:TableName>
<rd:TableAdapterFillMethod>Fill</rd:TableAdapterFillMethod>
<rd:TableAdapterGetDataMethod>GetData</rd:TableAdapterGetDataMethod>
<rd:TableAdapterName>SP_VentaMensualSecretariaElDiaTableAdapter</rd:TableAdapterName>
</rd:DataSetInfo>
</DataSet>
</DataSets>
<ReportSections>
<ReportSection>
<Body>
<ReportItems>
<Tablix Name="Tablix1">
<TablixBody>
<TablixColumns>
<TablixColumn>
<Width>1.5475cm</Width>
</TablixColumn>
<TablixColumn>
<Width>2.9525cm</Width>
</TablixColumn>
<TablixColumn>
<Width>2.85687cm</Width>
</TablixColumn>
<TablixColumn>
<Width>2.5cm</Width>
</TablixColumn>
<TablixColumn>
<Width>3.37313cm</Width>
</TablixColumn>
<TablixColumn>
<Width>3.27cm</Width>
</TablixColumn>
<TablixColumn>
<Width>2.5cm</Width>
</TablixColumn>
</TablixColumns>
<TablixRows>
<TablixRow>
<Height>1.20854cm</Height>
<TablixCells>
<TablixCell>
<CellContents>
<Textbox Name="Textbox5">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>DÍA</Value>
<Style>
<FontSize>12pt</FontSize>
<FontWeight>Bold</FontWeight>
<Color>White</Color>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Center</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox5</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<BackgroundColor>Black</BackgroundColor>
<VerticalAlign>Middle</VerticalAlign>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="Textbox7">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>CANILLAS</Value>
<Style>
<FontSize>12pt</FontSize>
<FontWeight>Bold</FontWeight>
<Color>White</Color>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Center</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox7</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<BackgroundColor>Black</BackgroundColor>
<VerticalAlign>Middle</VerticalAlign>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="Textbox9">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>TIRAJES</Value>
<Style>
<FontSize>12pt</FontSize>
<FontWeight>Bold</FontWeight>
<Color>White</Color>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Center</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox9</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<BackgroundColor>Black</BackgroundColor>
<VerticalAlign>Middle</VerticalAlign>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="Textbox1">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>VENTAS</Value>
<Style>
<FontSize>12pt</FontSize>
<FontWeight>Bold</FontWeight>
<Color>White</Color>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Center</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox1</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<BackgroundColor>Black</BackgroundColor>
<VerticalAlign>Middle</VerticalAlign>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="Textbox3">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>ACCIONISTAS</Value>
<Style>
<FontSize>12pt</FontSize>
<FontWeight>Bold</FontWeight>
<Color>White</Color>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Center</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox3</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<BackgroundColor>Black</BackgroundColor>
<VerticalAlign>Middle</VerticalAlign>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="Textbox6">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>TOTAL COOPERATIVA</Value>
<Style>
<FontSize>12pt</FontSize>
<FontWeight>Bold</FontWeight>
<Color>White</Color>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Center</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox6</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<BackgroundColor>Black</BackgroundColor>
<VerticalAlign>Middle</VerticalAlign>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="Textbox10">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>TOTAL</Value>
<Style>
<FontSize>12pt</FontSize>
<FontWeight>Bold</FontWeight>
<Color>White</Color>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Center</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox10</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<BackgroundColor>Black</BackgroundColor>
<VerticalAlign>Middle</VerticalAlign>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
</TablixCells>
</TablixRow>
<TablixRow>
<Height>0.75875cm</Height>
<TablixCells>
<TablixCell>
<CellContents>
<Textbox Name="Dia">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Fields!Dia.Value</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontSize>12pt</FontSize>
<FontWeight>Bold</FontWeight>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Center</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>Dia</rd:DefaultName>
<Style>
<Border>
<Style>Solid</Style>
</Border>
<VerticalAlign>Middle</VerticalAlign>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="Llevados">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Fields!CantidadCanillas.Value</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontSize>12pt</FontSize>
<Format>#,0;(#,0)</Format>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Center</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>Llevados</rd:DefaultName>
<Style>
<Border>
<Style>Solid</Style>
</Border>
<VerticalAlign>Middle</VerticalAlign>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="Devueltos">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Fields!Tirajes.Value</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontSize>12pt</FontSize>
<Format>#,0;(#,0)</Format>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Center</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>Devueltos</rd:DefaultName>
<Style>
<Border>
<Style>Solid</Style>
</Border>
<VerticalAlign>Middle</VerticalAlign>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="Ventas">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Fields!Ventas.Value</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontSize>12pt</FontSize>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Center</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>Ventas</rd:DefaultName>
<Style>
<Border>
<Style>Solid</Style>
</Border>
<VerticalAlign>Middle</VerticalAlign>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="Accionistas">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Fields!Accionistas.Value</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontSize>12pt</FontSize>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Center</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>Accionistas</rd:DefaultName>
<Style>
<Border>
<Style>Solid</Style>
</Border>
<VerticalAlign>Middle</VerticalAlign>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="TotalCooperativa">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Fields!TotalCooperativa.Value</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontSize>12pt</FontSize>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Center</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>TotalCooperativa</rd:DefaultName>
<Style>
<Border>
<Style>Solid</Style>
</Border>
<VerticalAlign>Middle</VerticalAlign>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="TotalGeneral">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Fields!TotalGeneral.Value</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontSize>12pt</FontSize>
<FontWeight>Bold</FontWeight>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Center</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>TotalGeneral</rd:DefaultName>
<Style>
<Border>
<Style>Solid</Style>
</Border>
<VerticalAlign>Middle</VerticalAlign>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
</TablixCells>
</TablixRow>
</TablixRows>
</TablixBody>
<TablixColumnHierarchy>
<TablixMembers>
<TablixMember />
<TablixMember />
<TablixMember />
<TablixMember />
<TablixMember />
<TablixMember />
<TablixMember />
</TablixMembers>
</TablixColumnHierarchy>
<TablixRowHierarchy>
<TablixMembers>
<TablixMember>
<KeepWithGroup>After</KeepWithGroup>
</TablixMember>
<TablixMember>
<Group Name="Detalles" />
</TablixMember>
</TablixMembers>
</TablixRowHierarchy>
<DataSetName>DSListadoDistribucion</DataSetName>
<Top>0.14139cm</Top>
<Left>0.619cm</Left>
<Height>1.96729cm</Height>
<Width>19cm</Width>
<Style>
<Border>
<Style>None</Style>
</Border>
</Style>
</Tablix>
</ReportItems>
<Height>0.88228in</Height>
<Style />
</Body>
<Width>20.70896cm</Width>
<Page>
<PageHeader>
<Height>1.89329cm</Height>
<PrintOnFirstPage>true</PrintOnFirstPage>
<PrintOnLastPage>true</PrintOnLastPage>
<ReportItems>
<Textbox Name="Textbox41">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>VENTA DIARIO EL DÍA</Value>
<Style>
<FontSize>16pt</FontSize>
<FontWeight>Bold</FontWeight>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Center</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox41</rd:DefaultName>
<Top>0.15522cm</Top>
<Left>0.619cm</Left>
<Height>0.72912cm</Height>
<Width>19cm</Width>
<Style>
<Border>
<Style>None</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
<Textbox Name="Textbox55">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>Fecha Consultada: Desde</Value>
<Style>
<FontSize>14pt</FontSize>
<FontWeight>Bold</FontWeight>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Left</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox41</rd:DefaultName>
<Top>1.07181cm</Top>
<Left>0.619cm</Left>
<Height>0.78267cm</Height>
<Width>7.14583cm</Width>
<ZIndex>1</ZIndex>
<Style>
<Border>
<Style>None</Style>
</Border>
<VerticalAlign>Middle</VerticalAlign>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
<Textbox Name="Textbox56">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=CDate(Parameters!FechaDesde.Value).ToString("dd/MM/yyyy")</Value>
<Style>
<FontSize>14pt</FontSize>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox49</rd:DefaultName>
<Top>1.10709cm</Top>
<Left>6.94192cm</Left>
<Height>0.73293cm</Height>
<Width>2.77341cm</Width>
<ZIndex>2</ZIndex>
<Style>
<Border>
<Style>None</Style>
</Border>
<VerticalAlign>Middle</VerticalAlign>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
<Textbox Name="Textbox57">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>Hasta</Value>
<Style>
<FontSize>14pt</FontSize>
<FontWeight>Bold</FontWeight>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Left</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox41</rd:DefaultName>
<Top>1.07181cm</Top>
<Left>10.13866cm</Left>
<Height>0.78267cm</Height>
<Width>1.56784cm</Width>
<ZIndex>3</ZIndex>
<Style>
<Border>
<Style>None</Style>
</Border>
<VerticalAlign>Middle</VerticalAlign>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
<Textbox Name="Textbox58">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=CDate(Parameters!FechaHasta.Value).ToString("dd/MM/yyyy")</Value>
<Style>
<FontSize>14pt</FontSize>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox49</rd:DefaultName>
<Top>1.10709cm</Top>
<Left>11.91074cm</Left>
<Height>0.73293cm</Height>
<Width>3.02917cm</Width>
<ZIndex>4</ZIndex>
<Style>
<Border>
<Style>None</Style>
</Border>
<VerticalAlign>Middle</VerticalAlign>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</ReportItems>
<Style>
<Border>
<Style>None</Style>
</Border>
</Style>
</PageHeader>
<PageHeight>29.7cm</PageHeight>
<PageWidth>21cm</PageWidth>
<LeftMargin>0cm</LeftMargin>
<RightMargin>0cm</RightMargin>
<TopMargin>0.5cm</TopMargin>
<BottomMargin>0.5cm</BottomMargin>
<ColumnSpacing>0.13cm</ColumnSpacing>
<Style />
</Page>
</ReportSection>
</ReportSections>
<ReportParameters>
<ReportParameter Name="FechaDesde">
<DataType>DateTime</DataType>
<Prompt>ReportParameter1</Prompt>
</ReportParameter>
<ReportParameter Name="FechaHasta">
<DataType>DateTime</DataType>
<Prompt>ReportParameter1</Prompt>
</ReportParameter>
</ReportParameters>
<ReportParametersLayout>
<GridLayoutDefinition>
<NumberOfColumns>4</NumberOfColumns>
<NumberOfRows>2</NumberOfRows>
<CellDefinitions>
<CellDefinition>
<ColumnIndex>2</ColumnIndex>
<RowIndex>0</RowIndex>
<ParameterName>FechaDesde</ParameterName>
</CellDefinition>
<CellDefinition>
<ColumnIndex>3</ColumnIndex>
<RowIndex>0</RowIndex>
<ParameterName>FechaHasta</ParameterName>
</CellDefinition>
</CellDefinitions>
</GridLayoutDefinition>
</ReportParametersLayout>
<rd:ReportUnitType>Cm</rd:ReportUnitType>
<rd:ReportID>8cf8fb7a-ec4d-4153-8fb0-a21a9e443a88</rd:ReportID>
</Report>

View File

@@ -1,879 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Report xmlns="http://schemas.microsoft.com/sqlserver/reporting/2016/01/reportdefinition" xmlns:rd="http://schemas.microsoft.com/SQLServer/reporting/reportdesigner">
<AutoRefresh>0</AutoRefresh>
<DataSources>
<DataSource Name="DSListadoDistribucion">
<ConnectionProperties>
<DataProvider>System.Data.DataSet</DataProvider>
<ConnectString>/* Local Connection */</ConnectString>
</ConnectionProperties>
<rd:DataSourceID>afdb4b97-a9ea-4bab-b77b-a7c9e72d6fac</rd:DataSourceID>
</DataSource>
</DataSources>
<DataSets>
<DataSet Name="DSListadoDistribucion">
<Query>
<DataSourceName>DSListadoDistribucion</DataSourceName>
<CommandText>/* Local Query */</CommandText>
</Query>
<Fields>
<Field Name="TiradaCoop">
<DataField>TiradaCoop</DataField>
<rd:TypeName>System.Int32</rd:TypeName>
</Field>
<Field Name="DevolucionCoop">
<DataField>DevolucionCoop</DataField>
<rd:TypeName>System.Int32</rd:TypeName>
</Field>
<Field Name="VentaCoop">
<DataField>VentaCoop</DataField>
<rd:TypeName>System.Int32</rd:TypeName>
</Field>
<Field Name="TiradaCan">
<DataField>TiradaCan</DataField>
<rd:TypeName>System.Int32</rd:TypeName>
</Field>
<Field Name="VentaCan">
<DataField>VentaCan</DataField>
<rd:TypeName>System.Int32</rd:TypeName>
</Field>
<Field Name="Total">
<DataField>Total</DataField>
<rd:TypeName>System.Int32</rd:TypeName>
</Field>
<Field Name="Dia">
<DataField>Dia</DataField>
<rd:TypeName>System.Int32</rd:TypeName>
</Field>
</Fields>
<rd:DataSetInfo>
<rd:DataSetName>DSListadoDistribucion</rd:DataSetName>
<rd:SchemaPath>C:\Users\dmolinari\source\repos\Cobol-VBNet\Reportes\DSListadoDistribucion.xsd</rd:SchemaPath>
<rd:TableName>SP_VentaMensualSecretariaElPlata</rd:TableName>
<rd:TableAdapterFillMethod>Fill</rd:TableAdapterFillMethod>
<rd:TableAdapterGetDataMethod>GetData</rd:TableAdapterGetDataMethod>
<rd:TableAdapterName>SP_VentaMensualSecretariaElPlataTableAdapter</rd:TableAdapterName>
</rd:DataSetInfo>
</DataSet>
</DataSets>
<ReportSections>
<ReportSection>
<Body>
<ReportItems>
<Tablix Name="Tablix1">
<TablixBody>
<TablixColumns>
<TablixColumn>
<Width>1.5475cm</Width>
</TablixColumn>
<TablixColumn>
<Width>2.9525cm</Width>
</TablixColumn>
<TablixColumn>
<Width>3.34396cm</Width>
</TablixColumn>
<TablixColumn>
<Width>2.79104cm</Width>
</TablixColumn>
<TablixColumn>
<Width>2.76459cm</Width>
</TablixColumn>
<TablixColumn>
<Width>2.75645cm</Width>
</TablixColumn>
<TablixColumn>
<Width>2.84396cm</Width>
</TablixColumn>
</TablixColumns>
<TablixRows>
<TablixRow>
<Height>1.63187cm</Height>
<TablixCells>
<TablixCell>
<CellContents>
<Textbox Name="Textbox2">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>Día</Value>
<Style>
<FontSize>12pt</FontSize>
<FontWeight>Bold</FontWeight>
<Color>White</Color>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Center</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox2</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<BackgroundColor>Black</BackgroundColor>
<VerticalAlign>Middle</VerticalAlign>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="Textbox7">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>TIRADA</Value>
<Style>
<FontSize>12pt</FontSize>
<FontWeight>Bold</FontWeight>
<Color>White</Color>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Center</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox7</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<BackgroundColor>Black</BackgroundColor>
<VerticalAlign>Middle</VerticalAlign>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="Textbox9">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>DEVOLUCIÓN COOP.</Value>
<Style>
<FontSize>12pt</FontSize>
<FontWeight>Bold</FontWeight>
<Color>White</Color>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Center</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox9</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<BackgroundColor>Black</BackgroundColor>
<VerticalAlign>Middle</VerticalAlign>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="Textbox1">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>VENTA COOP.</Value>
<Style>
<FontSize>12pt</FontSize>
<FontWeight>Bold</FontWeight>
<Color>White</Color>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Center</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox1</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<BackgroundColor>Black</BackgroundColor>
<VerticalAlign>Middle</VerticalAlign>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="Textbox3">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>TIRADA CANILLAS</Value>
<Style>
<FontSize>12pt</FontSize>
<FontWeight>Bold</FontWeight>
<Color>White</Color>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Center</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox3</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<BackgroundColor>Black</BackgroundColor>
<VerticalAlign>Middle</VerticalAlign>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="Textbox6">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>VENTA CANILLAS (TOTAL)</Value>
<Style>
<FontSize>12pt</FontSize>
<FontWeight>Bold</FontWeight>
<Color>White</Color>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Center</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox6</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<BackgroundColor>Black</BackgroundColor>
<VerticalAlign>Middle</VerticalAlign>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="Textbox10">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>TOTAL</Value>
<Style>
<FontSize>12pt</FontSize>
<FontWeight>Bold</FontWeight>
<Color>White</Color>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Center</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox10</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<BackgroundColor>Black</BackgroundColor>
<VerticalAlign>Middle</VerticalAlign>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
</TablixCells>
</TablixRow>
<TablixRow>
<Height>0.75875cm</Height>
<TablixCells>
<TablixCell>
<CellContents>
<Textbox Name="Dia1">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Fields!Dia.Value</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontSize>14pt</FontSize>
<FontWeight>Bold</FontWeight>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Center</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>Dia1</rd:DefaultName>
<Style>
<Border>
<Style>Solid</Style>
</Border>
<VerticalAlign>Middle</VerticalAlign>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="TiradaCoop">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Fields!TiradaCoop.Value</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontSize>14pt</FontSize>
<Format>#,0;(#,0)</Format>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Center</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>TiradaCoop</rd:DefaultName>
<Style>
<Border>
<Style>Solid</Style>
</Border>
<VerticalAlign>Middle</VerticalAlign>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="DevolucionCoop">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Fields!DevolucionCoop.Value</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontSize>14pt</FontSize>
<Format>#,0;(#,0)</Format>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Center</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>DevolucionCoop</rd:DefaultName>
<Style>
<Border>
<Style>Solid</Style>
</Border>
<VerticalAlign>Middle</VerticalAlign>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="VentaCoop">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Fields!VentaCoop.Value</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontSize>14pt</FontSize>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Center</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>VentaCoop</rd:DefaultName>
<Style>
<Border>
<Style>Solid</Style>
</Border>
<VerticalAlign>Middle</VerticalAlign>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="TiradaCan">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Fields!TiradaCan.Value</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontSize>14pt</FontSize>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Center</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>TiradaCan</rd:DefaultName>
<Style>
<Border>
<Style>Solid</Style>
</Border>
<VerticalAlign>Middle</VerticalAlign>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="VentaCan">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Fields!VentaCan.Value</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontSize>14pt</FontSize>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Center</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>VentaCan</rd:DefaultName>
<Style>
<Border>
<Style>Solid</Style>
</Border>
<VerticalAlign>Middle</VerticalAlign>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="Total">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Fields!Total.Value</Value>
<Style>
<FontFamily>Roboto</FontFamily>
<FontSize>14pt</FontSize>
<FontWeight>Bold</FontWeight>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Center</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>Total</rd:DefaultName>
<Style>
<Border>
<Style>Solid</Style>
</Border>
<VerticalAlign>Middle</VerticalAlign>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
</TablixCells>
</TablixRow>
</TablixRows>
</TablixBody>
<TablixColumnHierarchy>
<TablixMembers>
<TablixMember />
<TablixMember />
<TablixMember />
<TablixMember />
<TablixMember />
<TablixMember />
<TablixMember />
</TablixMembers>
</TablixColumnHierarchy>
<TablixRowHierarchy>
<TablixMembers>
<TablixMember>
<KeepWithGroup>After</KeepWithGroup>
</TablixMember>
<TablixMember>
<Group Name="Detalles" />
</TablixMember>
</TablixMembers>
</TablixRowHierarchy>
<DataSetName>DSListadoDistribucion</DataSetName>
<Top>0.14139cm</Top>
<Left>0.619cm</Left>
<Height>2.39062cm</Height>
<Width>19cm</Width>
<Style>
<Border>
<Style>None</Style>
</Border>
</Style>
</Tablix>
</ReportItems>
<Height>1.03853in</Height>
<Style />
</Body>
<Width>20.39921cm</Width>
<Page>
<PageHeader>
<Height>1.89329cm</Height>
<PrintOnFirstPage>true</PrintOnFirstPage>
<PrintOnLastPage>true</PrintOnLastPage>
<ReportItems>
<Textbox Name="Textbox41">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>VENTA DIARIO EL PLATA</Value>
<Style>
<FontSize>16pt</FontSize>
<FontWeight>Bold</FontWeight>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Center</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox41</rd:DefaultName>
<Top>0.15875cm</Top>
<Left>0.619cm</Left>
<Height>0.72912cm</Height>
<Width>19cm</Width>
<Style>
<Border>
<Style>None</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
<Textbox Name="Textbox55">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>Fecha Consultada: Desde</Value>
<Style>
<FontSize>14pt</FontSize>
<FontWeight>Bold</FontWeight>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Left</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox41</rd:DefaultName>
<Top>1.07534cm</Top>
<Left>0.619cm</Left>
<Height>0.78267cm</Height>
<Width>7.14583cm</Width>
<ZIndex>1</ZIndex>
<Style>
<Border>
<Style>None</Style>
</Border>
<VerticalAlign>Middle</VerticalAlign>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
<Textbox Name="Textbox56">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=CDate(Parameters!FechaDesde.Value).ToString("dd/MM/yyyy")</Value>
<Style>
<FontSize>14pt</FontSize>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox49</rd:DefaultName>
<Top>1.11062cm</Top>
<Left>6.94192cm</Left>
<Height>0.73293cm</Height>
<Width>2.77341cm</Width>
<ZIndex>2</ZIndex>
<Style>
<Border>
<Style>None</Style>
</Border>
<VerticalAlign>Middle</VerticalAlign>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
<Textbox Name="Textbox57">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>Hasta</Value>
<Style>
<FontSize>14pt</FontSize>
<FontWeight>Bold</FontWeight>
</Style>
</TextRun>
</TextRuns>
<Style>
<TextAlign>Left</TextAlign>
</Style>
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox41</rd:DefaultName>
<Top>1.07534cm</Top>
<Left>10.13866cm</Left>
<Height>0.78267cm</Height>
<Width>1.56784cm</Width>
<ZIndex>3</ZIndex>
<Style>
<Border>
<Style>None</Style>
</Border>
<VerticalAlign>Middle</VerticalAlign>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
<Textbox Name="Textbox58">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=CDate(Parameters!FechaHasta.Value).ToString("dd/MM/yyyy")</Value>
<Style>
<FontSize>14pt</FontSize>
</Style>
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox49</rd:DefaultName>
<Top>1.11062cm</Top>
<Left>11.91074cm</Left>
<Height>0.73293cm</Height>
<Width>3.02917cm</Width>
<ZIndex>4</ZIndex>
<Style>
<Border>
<Style>None</Style>
</Border>
<VerticalAlign>Middle</VerticalAlign>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</ReportItems>
<Style>
<Border>
<Style>None</Style>
</Border>
</Style>
</PageHeader>
<PageHeight>29.7cm</PageHeight>
<PageWidth>21cm</PageWidth>
<LeftMargin>0cm</LeftMargin>
<RightMargin>0cm</RightMargin>
<TopMargin>0.5cm</TopMargin>
<BottomMargin>0.5cm</BottomMargin>
<ColumnSpacing>0.13cm</ColumnSpacing>
<Style />
</Page>
</ReportSection>
</ReportSections>
<ReportParameters>
<ReportParameter Name="FechaDesde">
<DataType>DateTime</DataType>
<Prompt>ReportParameter1</Prompt>
</ReportParameter>
<ReportParameter Name="FechaHasta">
<DataType>DateTime</DataType>
<Prompt>ReportParameter1</Prompt>
</ReportParameter>
</ReportParameters>
<ReportParametersLayout>
<GridLayoutDefinition>
<NumberOfColumns>4</NumberOfColumns>
<NumberOfRows>2</NumberOfRows>
<CellDefinitions>
<CellDefinition>
<ColumnIndex>2</ColumnIndex>
<RowIndex>0</RowIndex>
<ParameterName>FechaDesde</ParameterName>
</CellDefinition>
<CellDefinition>
<ColumnIndex>3</ColumnIndex>
<RowIndex>0</RowIndex>
<ParameterName>FechaHasta</ParameterName>
</CellDefinition>
</CellDefinitions>
</GridLayoutDefinition>
</ReportParametersLayout>
<rd:ReportUnitType>Cm</rd:ReportUnitType>
<rd:ReportID>8cf8fb7a-ec4d-4153-8fb0-a21a9e443a88</rd:ReportID>
</Report>

View File

@@ -0,0 +1,94 @@
using GestionIntegral.Api.Dtos.Suscripciones;
using GestionIntegral.Api.Services.Suscripciones;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
namespace GestionIntegral.Api.Controllers.Suscripciones
{
[Route("api/ajustes")]
[ApiController]
[Authorize]
public class AjustesController : ControllerBase
{
private readonly IAjusteService _ajusteService;
private readonly ILogger<AjustesController> _logger;
// Permiso a crear en BD
private const string PermisoGestionarAjustes = "SU011";
public AjustesController(IAjusteService ajusteService, ILogger<AjustesController> logger)
{
_ajusteService = ajusteService;
_logger = logger;
}
private bool TienePermiso(string codAcc) => User.IsInRole("SuperAdmin") || User.HasClaim(c => c.Type == "permission" && c.Value == codAcc);
private int? GetCurrentUserId()
{
if (int.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub"), out int userId)) return userId;
return null;
}
// GET: api/suscriptores/{idSuscriptor}/ajustes
[HttpGet("~/api/suscriptores/{idSuscriptor:int}/ajustes")]
[ProducesResponseType(typeof(IEnumerable<AjusteDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetAjustesPorSuscriptor(int idSuscriptor, [FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta)
{
if (!TienePermiso(PermisoGestionarAjustes)) return Forbid();
var ajustes = await _ajusteService.ObtenerAjustesPorSuscriptor(idSuscriptor, fechaDesde, fechaHasta);
return Ok(ajustes);
}
// POST: api/ajustes
[HttpPost]
[ProducesResponseType(typeof(AjusteDto), StatusCodes.Status201Created)]
public async Task<IActionResult> CreateAjuste([FromBody] CreateAjusteDto createDto)
{
if (!TienePermiso(PermisoGestionarAjustes)) return Forbid();
if (!ModelState.IsValid) return BadRequest(ModelState);
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
var (dto, error) = await _ajusteService.CrearAjusteManual(createDto, userId.Value);
if (error != null) return BadRequest(new { message = error });
if (dto == null) return StatusCode(500, "Error al crear el ajuste.");
// Devolvemos el objeto creado con un 201
return StatusCode(201, dto);
}
// POST: api/ajustes/{id}/anular
[HttpPost("{id:int}/anular")]
public async Task<IActionResult> Anular(int id)
{
if (!TienePermiso(PermisoGestionarAjustes)) return Forbid();
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
var (exito, error) = await _ajusteService.AnularAjuste(id, userId.Value);
if (!exito) return BadRequest(new { message = error });
return Ok(new { message = "Ajuste anulado correctamente." });
}
// PUT: api/ajustes/{id}
[HttpPut("{id:int}")]
public async Task<IActionResult> UpdateAjuste(int id, [FromBody] UpdateAjusteDto updateDto)
{
if (!TienePermiso(PermisoGestionarAjustes)) return Forbid();
if (!ModelState.IsValid) return BadRequest(ModelState);
var (exito, error) = await _ajusteService.ActualizarAjuste(id, updateDto);
if (!exito)
{
if (error != null && error.Contains("no encontrado")) return NotFound(new { message = error });
return BadRequest(new { message = error });
}
return NoContent();
}
}
}

View File

@@ -0,0 +1,93 @@
// Archivo: GestionIntegral.Api/Controllers/Suscripciones/DebitosController.cs
using GestionIntegral.Api.Dtos.Suscripciones;
using GestionIntegral.Api.Services.Suscripciones;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
using System.Text;
namespace GestionIntegral.Api.Controllers.Suscripciones
{
[Route("api/debitos")]
[ApiController]
[Authorize]
public class DebitosController : ControllerBase
{
private readonly IDebitoAutomaticoService _debitoService;
private readonly ILogger<DebitosController> _logger;
// Permiso para generar archivos de débito (a crear en BD)
private const string PermisoGenerarDebitos = "SU007";
public DebitosController(IDebitoAutomaticoService debitoService, ILogger<DebitosController> logger)
{
_debitoService = debitoService;
_logger = logger;
}
private bool TienePermiso(string codAcc) => User.IsInRole("SuperAdmin") || User.HasClaim(c => c.Type == "permission" && c.Value == codAcc);
private int? GetCurrentUserId()
{
if (int.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub"), out int userId)) return userId;
return null;
}
// POST: api/debitos/{anio}/{mes}/generar-archivo
[HttpPost("{anio:int}/{mes:int}/generar-archivo")]
[ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> GenerarArchivo(int anio, int mes)
{
if (!TienePermiso(PermisoGenerarDebitos)) return Forbid();
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
var (contenido, nombreArchivo, error) = await _debitoService.GenerarArchivoPagoDirecto(anio, mes, userId.Value);
if (error != null)
{
// Si el error es "No se encontraron facturas", es un 404. Otros son 400.
if (error.Contains("No se encontraron"))
{
return NotFound(new { message = error });
}
return BadRequest(new { message = error });
}
if (string.IsNullOrEmpty(contenido) || string.IsNullOrEmpty(nombreArchivo))
{
return StatusCode(500, new { message = "El servicio no pudo generar el contenido del archivo correctamente." });
}
// Devolver el archivo para descarga
var fileBytes = Encoding.UTF8.GetBytes(contenido);
return File(fileBytes, "text/plain", nombreArchivo);
}
// POST: api/debitos/procesar-respuesta
[HttpPost("procesar-respuesta")]
[ProducesResponseType(typeof(ProcesamientoLoteResponseDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> ProcesarArchivoRespuesta(IFormFile archivo)
{
// Usamos el mismo permiso de generar débitos para procesar la respuesta.
if (!TienePermiso(PermisoGenerarDebitos)) return Forbid();
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
var resultado = await _debitoService.ProcesarArchivoRespuesta(archivo, userId.Value);
if (resultado.Errores.Any() && resultado.PagosAprobados == 0 && resultado.PagosRechazados == 0)
{
return BadRequest(resultado);
}
return Ok(resultado);
}
}
}

View File

@@ -0,0 +1,126 @@
using GestionIntegral.Api.Dtos.Comunicaciones;
using GestionIntegral.Api.Services.Comunicaciones;
using GestionIntegral.Api.Services.Suscripciones;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
namespace GestionIntegral.Api.Controllers.Suscripciones
{
[Route("api/facturacion")]
[ApiController]
[Authorize]
public class FacturacionController : ControllerBase
{
private readonly IFacturacionService _facturacionService;
private readonly ILogger<FacturacionController> _logger;
private readonly IEmailLogService _emailLogService;
private const string PermisoGestionarFacturacion = "SU006";
private const string PermisoEnviarEmail = "SU009";
public FacturacionController(IFacturacionService facturacionService, ILogger<FacturacionController> logger, IEmailLogService emailLogService)
{
_facturacionService = facturacionService;
_logger = logger;
_emailLogService = emailLogService;
}
private bool TienePermiso(string codAcc) => User.IsInRole("SuperAdmin") || User.HasClaim(c => c.Type == "permission" && c.Value == codAcc);
private int? GetCurrentUserId()
{
if (int.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub"), out int userId)) return userId;
_logger.LogWarning("No se pudo obtener el UserId del token JWT en FacturacionController.");
return null;
}
[HttpPut("{idFactura:int}/numero-factura")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<IActionResult> UpdateNumeroFactura(int idFactura, [FromBody] string numeroFactura)
{
if (!TienePermiso(PermisoGestionarFacturacion)) return Forbid();
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
var (exito, error) = await _facturacionService.ActualizarNumeroFactura(idFactura, numeroFactura, userId.Value);
if (!exito)
{
if (error != null && error.Contains("no existe")) return NotFound(new { message = error });
return BadRequest(new { message = error });
}
return NoContent();
}
[HttpPost("{idFactura:int}/enviar-factura-pdf")]
public async Task<IActionResult> EnviarFacturaPdf(int idFactura)
{
if (!TienePermiso(PermisoEnviarEmail)) return Forbid();
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
var (exito, error, emailDestino) = await _facturacionService.EnviarFacturaPdfPorEmail(idFactura, userId.Value);
if (!exito)
{
return BadRequest(new { message = error });
}
var mensajeExito = $"El email con la factura PDF se ha enviado correctamente a {emailDestino}.";
return Ok(new { message = mensajeExito });
}
[HttpGet("{anio:int}/{mes:int}")]
public async Task<IActionResult> GetFacturas(
int anio, int mes,
[FromQuery] string? nombreSuscriptor,
[FromQuery] string? estadoPago,
[FromQuery] string? estadoFacturacion,
[FromQuery] string? tipoFactura)
{
if (!TienePermiso(PermisoGestionarFacturacion)) return Forbid();
if (anio < 2020 || mes < 1 || mes > 12) return BadRequest(new { message = "El período no es válido." });
var resumenes = await _facturacionService.ObtenerResumenesDeCuentaPorPeriodo(anio, mes, nombreSuscriptor, estadoPago, estadoFacturacion, tipoFactura);
return Ok(resumenes);
}
[HttpPost("{anio:int}/{mes:int}")]
public async Task<IActionResult> GenerarFacturacion(int anio, int mes)
{
if (!TienePermiso(PermisoGestionarFacturacion)) return Forbid();
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
if (anio < 2020 || mes < 1 || mes > 12) return BadRequest(new { message = "El año y el mes proporcionados no son válidos." });
var (exito, mensaje, resultadoEnvio) = await _facturacionService.GenerarFacturacionMensual(anio, mes, userId.Value);
if (!exito) return StatusCode(StatusCodes.Status500InternalServerError, new { message = mensaje });
return Ok(new { message = mensaje, resultadoEnvio });
}
[HttpGet("historial-lotes-envio")]
[ProducesResponseType(typeof(IEnumerable<LoteDeEnvioHistorialDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetHistorialLotesEnvio([FromQuery] int? anio, [FromQuery] int? mes)
{
if (!TienePermiso("SU006")) return Forbid();
var historial = await _facturacionService.ObtenerHistorialLotesEnvio(anio, mes);
return Ok(historial);
}
// Endpoint para el historial de envíos de una factura individual
[HttpGet("{idFactura:int}/historial-envios")]
[ProducesResponseType(typeof(IEnumerable<EmailLogDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetHistorialEnvios(int idFactura)
{
if (!TienePermiso(PermisoGestionarFacturacion)) return Forbid(); // Reutilizamos el permiso
// Construimos la referencia que se guarda en el log
string referencia = $"Factura-{idFactura}";
var historial = await _emailLogService.ObtenerHistorialPorReferencia(referencia);
return Ok(historial);
}
}
}

View File

@@ -0,0 +1,27 @@
using GestionIntegral.Api.Services.Suscripciones;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace GestionIntegral.Api.Controllers.Suscripciones
{
[Route("api/formaspago")]
[ApiController]
[Authorize] // Solo usuarios logueados pueden ver esto
public class FormasDePagoController : ControllerBase
{
private readonly IFormaPagoService _formaPagoService;
public FormasDePagoController(IFormaPagoService formaPagoService)
{
_formaPagoService = formaPagoService;
}
// GET: api/formaspago
[HttpGet]
public async Task<IActionResult> GetAll()
{
var formasDePago = await _formaPagoService.ObtenerTodos();
return Ok(formasDePago);
}
}
}

View File

@@ -0,0 +1,69 @@
using GestionIntegral.Api.Dtos.Suscripciones;
using GestionIntegral.Api.Services.Suscripciones;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
namespace GestionIntegral.Api.Controllers.Suscripciones
{
[Route("api/pagos")]
[ApiController]
[Authorize]
public class PagosController : ControllerBase
{
private readonly IPagoService _pagoService;
private readonly ILogger<PagosController> _logger;
// Permiso para registrar pagos manuales (a crear en BD)
private const string PermisoRegistrarPago = "SU008";
public PagosController(IPagoService pagoService, ILogger<PagosController> logger)
{
_pagoService = pagoService;
_logger = logger;
}
private bool TienePermiso(string codAcc) => User.IsInRole("SuperAdmin") || User.HasClaim(c => c.Type == "permission" && c.Value == codAcc);
private int? GetCurrentUserId()
{
if (int.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub"), out int userId)) return userId;
return null;
}
// GET: api/facturas/{idFactura}/pagos
[HttpGet("~/api/facturas/{idFactura:int}/pagos")]
[ProducesResponseType(typeof(IEnumerable<PagoDto>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> GetPagosPorFactura(int idFactura)
{
// Se podría usar un permiso de "Ver Facturación"
if (!TienePermiso("SU006")) return Forbid();
var pagos = await _pagoService.ObtenerPagosPorFacturaId(idFactura);
return Ok(pagos);
}
// POST: api/pagos
[HttpPost]
[ProducesResponseType(typeof(PagoDto), StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> RegistrarPago([FromBody] CreatePagoDto createDto)
{
if (!TienePermiso(PermisoRegistrarPago)) return Forbid();
if (!ModelState.IsValid) return BadRequest(ModelState);
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
var (dto, error) = await _pagoService.RegistrarPagoManual(createDto, userId.Value);
if (error != null) return BadRequest(new { message = error });
if (dto == null) return StatusCode(StatusCodes.Status500InternalServerError, "Error al registrar el pago.");
// No tenemos un "GetById" para pagos, así que devolvemos el objeto con un 201.
return StatusCode(201, dto);
}
}
}

View File

@@ -0,0 +1,90 @@
using GestionIntegral.Api.Dtos.Suscripciones;
using GestionIntegral.Api.Services.Suscripciones;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
namespace GestionIntegral.Api.Controllers.Suscripciones
{
[Route("api/promociones")]
[ApiController]
[Authorize]
public class PromocionesController : ControllerBase
{
private readonly IPromocionService _promocionService;
private readonly ILogger<PromocionesController> _logger;
// Permiso a crear en BD
private const string PermisoGestionarPromociones = "SU010";
public PromocionesController(IPromocionService promocionService, ILogger<PromocionesController> logger)
{
_promocionService = promocionService;
_logger = logger;
}
private bool TienePermiso(string codAcc) => User.IsInRole("SuperAdmin") || User.HasClaim(c => c.Type == "permission" && c.Value == codAcc);
private int? GetCurrentUserId()
{
if (int.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub"), out int userId)) return userId;
return null;
}
// GET: api/promociones
[HttpGet]
public async Task<IActionResult> GetAll([FromQuery] bool soloActivas = true)
{
if (!TienePermiso(PermisoGestionarPromociones)) return Forbid();
var promociones = await _promocionService.ObtenerTodas(soloActivas);
return Ok(promociones);
}
// GET: api/promociones/{id}
[HttpGet("{id:int}", Name = "GetPromocionById")]
public async Task<IActionResult> GetById(int id)
{
if (!TienePermiso(PermisoGestionarPromociones)) return Forbid();
var promocion = await _promocionService.ObtenerPorId(id);
if (promocion == null) return NotFound();
return Ok(promocion);
}
// POST: api/promociones
[HttpPost]
public async Task<IActionResult> Create([FromBody] CreatePromocionDto createDto)
{
if (!TienePermiso(PermisoGestionarPromociones)) return Forbid();
if (!ModelState.IsValid) return BadRequest(ModelState);
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
var (dto, error) = await _promocionService.Crear(createDto, userId.Value);
if (error != null) return BadRequest(new { message = error });
if (dto == null) return StatusCode(500, "Error al crear la promoción.");
return CreatedAtRoute("GetPromocionById", new { id = dto.IdPromocion }, dto);
}
// PUT: api/promociones/{id}
[HttpPut("{id:int}")]
public async Task<IActionResult> Update(int id, [FromBody] UpdatePromocionDto updateDto)
{
if (!TienePermiso(PermisoGestionarPromociones)) return Forbid();
if (!ModelState.IsValid) return BadRequest(ModelState);
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
var (exito, error) = await _promocionService.Actualizar(id, updateDto, userId.Value);
if (!exito)
{
if (error != null && error.Contains("no encontrada")) return NotFound(new { message = error });
return BadRequest(new { message = error });
}
return NoContent();
}
}
}

View File

@@ -0,0 +1,138 @@
// Archivo: GestionIntegral.Api/Controllers/Suscripciones/SuscripcionesController.cs
using GestionIntegral.Api.Dtos.Suscripciones;
using GestionIntegral.Api.Services.Suscripciones;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
namespace GestionIntegral.Api.Controllers.Suscripciones
{
[Route("api/suscripciones")] // Ruta base para acciones sobre una suscripción específica
[ApiController]
[Authorize]
public class SuscripcionesController : ControllerBase
{
private readonly ISuscripcionService _suscripcionService;
private readonly ILogger<SuscripcionesController> _logger;
// Permisos (nuevos, a crear en la BD)
private const string PermisoGestionarSuscripciones = "SU005";
public SuscripcionesController(ISuscripcionService suscripcionService, ILogger<SuscripcionesController> logger)
{
_suscripcionService = suscripcionService;
_logger = logger;
}
private bool TienePermiso(string codAcc) => User.IsInRole("SuperAdmin") || User.HasClaim(c => c.Type == "permission" && c.Value == codAcc);
private int? GetCurrentUserId()
{
if (int.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub"), out int userId)) return userId;
_logger.LogWarning("No se pudo obtener el UserId del token JWT en SuscripcionesController.");
return null;
}
// Endpoint anidado para obtener las suscripciones de un suscriptor
// GET: api/suscriptores/{idSuscriptor}/suscripciones
[HttpGet("~/api/suscriptores/{idSuscriptor:int}/suscripciones")]
public async Task<IActionResult> GetBySuscriptor(int idSuscriptor)
{
// Se podría usar el permiso de ver suscriptores (SU001) o el de gestionar suscripciones (SU005)
if (!TienePermiso("SU001")) return Forbid();
var suscripciones = await _suscripcionService.ObtenerPorSuscriptorId(idSuscriptor);
return Ok(suscripciones);
}
// GET: api/suscripciones/{id}
[HttpGet("{id:int}", Name = "GetSuscripcionById")]
public async Task<IActionResult> GetById(int id)
{
if (!TienePermiso(PermisoGestionarSuscripciones)) return Forbid();
var suscripcion = await _suscripcionService.ObtenerPorId(id);
if (suscripcion == null) return NotFound();
return Ok(suscripcion);
}
// POST: api/suscripciones
[HttpPost]
public async Task<IActionResult> Create([FromBody] CreateSuscripcionDto createDto)
{
if (!TienePermiso(PermisoGestionarSuscripciones)) return Forbid();
if (!ModelState.IsValid) return BadRequest(ModelState);
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
var (dto, error) = await _suscripcionService.Crear(createDto, userId.Value);
if (error != null) return BadRequest(new { message = error });
if (dto == null) return StatusCode(StatusCodes.Status500InternalServerError, "Error al crear la suscripción.");
return CreatedAtRoute("GetSuscripcionById", new { id = dto.IdSuscripcion }, dto);
}
// PUT: api/suscripciones/{id}
[HttpPut("{id:int}")]
public async Task<IActionResult> Update(int id, [FromBody] UpdateSuscripcionDto updateDto)
{
if (!TienePermiso(PermisoGestionarSuscripciones)) return Forbid();
if (!ModelState.IsValid) return BadRequest(ModelState);
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
var (exito, error) = await _suscripcionService.Actualizar(id, updateDto, userId.Value);
if (!exito)
{
if (error != null && error.Contains("no encontrada")) return NotFound(new { message = error });
return BadRequest(new { message = error });
}
return NoContent();
}
// GET: api/suscripciones/{idSuscripcion}/promociones
[HttpGet("{idSuscripcion:int}/promociones")]
public async Task<IActionResult> GetPromocionesAsignadas(int idSuscripcion)
{
if (!TienePermiso(PermisoGestionarSuscripciones)) return Forbid();
var promos = await _suscripcionService.ObtenerPromocionesAsignadas(idSuscripcion);
return Ok(promos);
}
// GET: api/suscripciones/{idSuscripcion}/promociones-disponibles
[HttpGet("{idSuscripcion:int}/promociones-disponibles")]
public async Task<IActionResult> GetPromocionesDisponibles(int idSuscripcion)
{
if (!TienePermiso(PermisoGestionarSuscripciones)) return Forbid();
var promos = await _suscripcionService.ObtenerPromocionesDisponibles(idSuscripcion);
return Ok(promos);
}
// POST: api/suscripciones/{idSuscripcion}/promociones
[HttpPost("{idSuscripcion:int}/promociones")]
public async Task<IActionResult> AsignarPromocion(int idSuscripcion, [FromBody] AsignarPromocionDto dto)
{
if (!TienePermiso(PermisoGestionarSuscripciones)) return Forbid();
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
var (exito, error) = await _suscripcionService.AsignarPromocion(idSuscripcion, dto, userId.Value);
if (!exito) return BadRequest(new { message = error });
return Ok();
}
// DELETE: api/suscripciones/{idSuscripcion}/promociones/{idPromocion}
[HttpDelete("{idSuscripcion:int}/promociones/{idPromocion:int}")]
public async Task<IActionResult> QuitarPromocion(int idSuscripcion, int idPromocion)
{
if (!TienePermiso(PermisoGestionarSuscripciones)) return Forbid();
var (exito, error) = await _suscripcionService.QuitarPromocion(idSuscripcion, idPromocion);
if (!exito) return BadRequest(new { message = error });
return NoContent();
}
}
}

View File

@@ -0,0 +1,153 @@
using GestionIntegral.Api.Dtos.Suscripciones;
using GestionIntegral.Api.Services.Suscripciones;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
namespace GestionIntegral.Api.Controllers.Suscripciones
{
[Route("api/suscriptores")]
[ApiController]
[Authorize]
public class SuscriptoresController : ControllerBase
{
private readonly ISuscriptorService _suscriptorService;
private readonly ILogger<SuscriptoresController> _logger;
// Permisos para Suscriptores
private const string PermisoVer = "SU001";
private const string PermisoCrear = "SU002";
private const string PermisoModificar = "SU003";
private const string PermisoActivarDesactivar = "SU004";
public SuscriptoresController(ISuscriptorService suscriptorService, ILogger<SuscriptoresController> logger)
{
_suscriptorService = suscriptorService;
_logger = logger;
}
private bool TienePermiso(string codAcc) => User.IsInRole("SuperAdmin") || User.HasClaim(c => c.Type == "permission" && c.Value == codAcc);
private int? GetCurrentUserId()
{
if (int.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub"), out int userId)) return userId;
_logger.LogWarning("No se pudo obtener el UserId del token JWT en SuscriptoresController.");
return null;
}
// GET: api/suscriptores
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<SuscriptorDto>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> GetAll([FromQuery] string? nombre, [FromQuery] string? nroDoc, [FromQuery] bool soloActivos = true)
{
if (!TienePermiso(PermisoVer)) return Forbid();
var suscriptores = await _suscriptorService.ObtenerTodos(nombre, nroDoc, soloActivos);
return Ok(suscriptores);
}
// GET: api/suscriptores/{id}
[HttpGet("{id:int}", Name = "GetSuscriptorById")]
[ProducesResponseType(typeof(SuscriptorDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetById(int id)
{
if (!TienePermiso(PermisoVer)) return Forbid();
var suscriptor = await _suscriptorService.ObtenerPorId(id);
if (suscriptor == null) return NotFound();
return Ok(suscriptor);
}
// POST: api/suscriptores
[HttpPost]
[ProducesResponseType(typeof(SuscriptorDto), StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> Create([FromBody] CreateSuscriptorDto createDto)
{
if (!TienePermiso(PermisoCrear)) return Forbid();
if (!ModelState.IsValid) return BadRequest(ModelState);
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
var (dto, error) = await _suscriptorService.Crear(createDto, userId.Value);
if (error != null) return BadRequest(new { message = error });
if (dto == null) return StatusCode(StatusCodes.Status500InternalServerError, "Error al crear el suscriptor.");
return CreatedAtRoute("GetSuscriptorById", new { id = dto.IdSuscriptor }, dto);
}
// PUT: api/suscriptores/{id}
[HttpPut("{id:int}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> Update(int id, [FromBody] UpdateSuscriptorDto updateDto)
{
if (!TienePermiso(PermisoModificar)) return Forbid();
if (!ModelState.IsValid) return BadRequest(ModelState);
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
var (exito, error) = await _suscriptorService.Actualizar(id, updateDto, userId.Value);
if (!exito)
{
if (error != null && error.Contains("no encontrado")) return NotFound(new { message = error });
return BadRequest(new { message = error });
}
return NoContent();
}
// DELETE: api/suscriptores/{id} (Desactivar)
[HttpDelete("{id:int}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> Deactivate(int id)
{
if (!TienePermiso(PermisoActivarDesactivar)) return Forbid();
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
var (exito, error) = await _suscriptorService.Desactivar(id, userId.Value);
if (!exito)
{
if (error != null && error.Contains("no encontrado")) return NotFound(new { message = error });
return BadRequest(new { message = error });
}
return NoContent();
}
// POST: api/suscriptores/{id}/activar
[HttpPost("{id:int}/activar")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> Activate(int id)
{
if (!TienePermiso(PermisoActivarDesactivar)) return Forbid();
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
var (exito, error) = await _suscriptorService.Activar(id, userId.Value);
if (!exito)
{
if (error != null && error.Contains("no encontrado")) return NotFound(new { message = error });
return BadRequest(new { message = error });
}
return NoContent();
}
}
}

View File

@@ -0,0 +1,65 @@
using Dapper;
using GestionIntegral.Api.Models.Comunicaciones;
namespace GestionIntegral.Api.Data.Repositories.Comunicaciones
{
public class EmailLogRepository : IEmailLogRepository
{
private readonly DbConnectionFactory _connectionFactory;
private readonly ILogger<EmailLogRepository> _logger;
public EmailLogRepository(DbConnectionFactory connectionFactory, ILogger<EmailLogRepository> logger)
{
_connectionFactory = connectionFactory;
_logger = logger;
}
public async Task CreateAsync(EmailLog log)
{
const string sql = @"
INSERT INTO dbo.com_EmailLogs
(FechaEnvio, DestinatarioEmail, Asunto, Estado, Error, IdUsuarioDisparo, Origen, ReferenciaId, IdLoteDeEnvio)
VALUES
(@FechaEnvio, @DestinatarioEmail, @Asunto, @Estado, @Error, @IdUsuarioDisparo, @Origen, @ReferenciaId, @IdLoteDeEnvio);";
using var connection = _connectionFactory.CreateConnection();
await connection.ExecuteAsync(sql, log);
}
public async Task<IEnumerable<EmailLog>> GetByReferenceAsync(string referenciaId)
{
const string sql = @"
SELECT * FROM dbo.com_EmailLogs
WHERE ReferenciaId = @ReferenciaId
ORDER BY FechaEnvio DESC;";
try
{
using var connection = _connectionFactory.CreateConnection();
return await connection.QueryAsync<EmailLog>(sql, new { ReferenciaId = referenciaId });
}
catch (System.Exception ex)
{
_logger.LogError(ex, "Error al obtener logs de email por ReferenciaId: {ReferenciaId}", referenciaId);
return Enumerable.Empty<EmailLog>();
}
}
public async Task<IEnumerable<EmailLog>> GetByLoteIdAsync(int idLoteDeEnvio)
{
// Ordenamos por Estado descendente para que los 'Fallidos' aparezcan primero
const string sql = @"
SELECT * FROM dbo.com_EmailLogs
WHERE IdLoteDeEnvio = @IdLoteDeEnvio
ORDER BY Estado DESC, FechaEnvio DESC;";
try
{
using var connection = _connectionFactory.CreateConnection();
return await connection.QueryAsync<EmailLog>(sql, new { IdLoteDeEnvio = idLoteDeEnvio });
}
catch (Exception ex)
{
_logger.LogError(ex, "Error al obtener logs de email por IdLoteDeEnvio: {IdLoteDeEnvio}", idLoteDeEnvio);
return Enumerable.Empty<EmailLog>();
}
}
}
}

View File

@@ -0,0 +1,26 @@
using GestionIntegral.Api.Models.Comunicaciones;
namespace GestionIntegral.Api.Data.Repositories.Comunicaciones
{
public interface IEmailLogRepository
{
/// <summary>
/// Guarda un nuevo registro de log de email en la base de datos.
/// </summary>
Task CreateAsync(EmailLog log);
/// <summary>
/// Obtiene todos los registros de log de email que coinciden con una referencia específica.
/// </summary>
/// <param name="referenciaId">El identificador de la entidad (ej. "Factura-59").</param>
/// <returns>Una colección de registros de log de email.</returns>
Task<IEnumerable<EmailLog>> GetByReferenceAsync(string referenciaId);
/// <summary>
/// Obtiene todos los registros de log de email que pertenecen a un lote de envío masivo.
/// </summary>
/// <param name="idLoteDeEnvio">El ID del lote de envío.</param>
/// <returns>Una colección de registros de log de email.</returns>
Task<IEnumerable<EmailLog>> GetByLoteIdAsync(int idLoteDeEnvio);
}
}

View File

@@ -0,0 +1,12 @@
using GestionIntegral.Api.Models.Comunicaciones;
namespace GestionIntegral.Api.Data.Repositories.Comunicaciones
{
public interface ILoteDeEnvioRepository
{
Task<LoteDeEnvio> CreateAsync(LoteDeEnvio lote);
Task<bool> UpdateAsync(LoteDeEnvio lote);
Task<IEnumerable<LoteDeEnvio>> GetAllAsync(int? anio, int? mes);
Task<LoteDeEnvio?> GetByIdAsync(int id);
}
}

View File

@@ -0,0 +1,69 @@
using System.Text;
using Dapper;
using GestionIntegral.Api.Models.Comunicaciones;
namespace GestionIntegral.Api.Data.Repositories.Comunicaciones
{
public class LoteDeEnvioRepository : ILoteDeEnvioRepository
{
private readonly DbConnectionFactory _connectionFactory;
public LoteDeEnvioRepository(DbConnectionFactory connectionFactory)
{
_connectionFactory = connectionFactory;
}
public async Task<LoteDeEnvio> CreateAsync(LoteDeEnvio lote)
{
const string sql = @"
INSERT INTO dbo.com_LotesDeEnvio (FechaInicio, Periodo, Origen, Estado, IdUsuarioDisparo)
OUTPUT INSERTED.*
VALUES (@FechaInicio, @Periodo, @Origen, @Estado, @IdUsuarioDisparo);";
using var connection = _connectionFactory.CreateConnection();
return await connection.QuerySingleAsync<LoteDeEnvio>(sql, lote);
}
public async Task<bool> UpdateAsync(LoteDeEnvio lote)
{
const string sql = @"
UPDATE dbo.com_LotesDeEnvio SET
FechaFin = @FechaFin,
Estado = @Estado,
TotalCorreos = @TotalCorreos,
TotalEnviados = @TotalEnviados,
TotalFallidos = @TotalFallidos
WHERE IdLoteDeEnvio = @IdLoteDeEnvio;";
using var connection = _connectionFactory.CreateConnection();
var rows = await connection.ExecuteAsync(sql, lote);
return rows == 1;
}
public async Task<IEnumerable<LoteDeEnvio>> GetAllAsync(int? anio, int? mes)
{
var sqlBuilder = new StringBuilder("SELECT * FROM dbo.com_LotesDeEnvio WHERE 1=1");
var parameters = new DynamicParameters();
if (anio.HasValue)
{
sqlBuilder.Append(" AND YEAR(FechaInicio) = @Anio");
parameters.Add("Anio", anio.Value);
}
if (mes.HasValue)
{
sqlBuilder.Append(" AND MONTH(FechaInicio) = @Mes");
parameters.Add("Mes", mes.Value);
}
sqlBuilder.Append(" ORDER BY FechaInicio DESC;");
using var connection = _connectionFactory.CreateConnection();
return await connection.QueryAsync<LoteDeEnvio>(sqlBuilder.ToString(), parameters);
}
public async Task<LoteDeEnvio?> GetByIdAsync(int id)
{
const string sql = "SELECT * FROM dbo.com_LotesDeEnvio WHERE IdLoteDeEnvio = @Id;";
using var connection = _connectionFactory.CreateConnection();
return await connection.QuerySingleOrDefaultAsync<LoteDeEnvio>(sql, new { Id = id });
}
}
}

View File

@@ -16,6 +16,9 @@ namespace GestionIntegral.Api.Data.Repositories.Contables
Task<NotaCreditoDebito?> CreateAsync(NotaCreditoDebito nuevaNota, int idUsuario, IDbTransaction transaction);
Task<bool> UpdateAsync(NotaCreditoDebito notaAActualizar, int idUsuario, IDbTransaction transaction);
Task<bool> DeleteAsync(int idNota, int idUsuario, IDbTransaction transaction);
// No se suele validar unicidad por referencia, ya que podría repetirse.
Task<IEnumerable<(NotaCreditoDebitoHistorico Historial, string NombreUsuarioModifico)>> GetHistorialAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idUsuarioModifico, string? tipoModificacion,
int? idNotaOriginal); // Para filtrar por una nota específica
}
}

View File

@@ -17,5 +17,9 @@ namespace GestionIntegral.Api.Data.Repositories.Contables
Task<bool> UpdateAsync(PagoDistribuidor pagoAActualizar, int idUsuario, IDbTransaction transaction);
Task<bool> DeleteAsync(int idPago, int idUsuario, IDbTransaction transaction);
Task<bool> ExistsByReciboAndTipoMovimientoAsync(int recibo, string tipoMovimiento, int? excludeIdPago = null);
Task<IEnumerable<(PagoDistribuidorHistorico Historial, string NombreUsuarioModifico)>> GetHistorialAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idUsuarioModifico, string? tipoModificacion,
int? idPagoOriginal); // Para filtrar por un pago específico
}
}

View File

@@ -1,6 +1,8 @@
using System.Threading.Tasks;
using System.Collections.Generic; // Para IEnumerable
using System.Data;
using GestionIntegral.Api.Dtos.Contables; // Para SaldoGestionDto si lo usas aquí
using GestionIntegral.Api.Models.Contables; // Para Saldo, SaldoAjusteHistorial
namespace GestionIntegral.Api.Data.Repositories.Contables
{
@@ -15,5 +17,16 @@ namespace GestionIntegral.Api.Data.Repositories.Contables
// Método para modificar saldo (lo teníamos como privado antes, ahora en el repo)
Task<bool> ModificarSaldoAsync(string destino, int idDestino, int idEmpresa, decimal montoAAgregar, IDbTransaction? transaction = null);
Task<bool> CheckIfSaldosExistForEmpresaAsync(int id);
// Para obtener la lista de saldos para la página de gestión
Task<IEnumerable<Saldo>> GetSaldosParaGestionAsync(string? destinoFilter, int? idDestinoFilter, int? idEmpresaFilter);
// Para obtener un saldo específico (ya podría existir uno similar, o crearlo si es necesario)
Task<Saldo?> GetSaldoAsync(string destino, int idDestino, int idEmpresa, IDbTransaction? transaction = null);
// Para registrar el historial de ajuste
Task CreateSaldoAjusteHistorialAsync(SaldoAjusteHistorial historialEntry, IDbTransaction transaction);
Task<IEnumerable<(SaldoAjusteHistorial Historial, string NombreUsuarioModifico)>> GetHistorialAjustesAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idUsuarioModifico,
string? destino, int? idDestino, int? idEmpresa);
}
}

View File

@@ -13,5 +13,9 @@ namespace GestionIntegral.Api.Data.Repositories.Contables
Task<bool> DeleteAsync(int id, int idUsuario); // Devuelve true si fue exitoso
Task<bool> ExistsByNameAsync(string nombre, int? excludeId = null);
Task<bool> IsInUseAsync(int id);
Task<IEnumerable<(TipoPagoHistorico Historial, string NombreUsuarioModifico)>> GetHistorialAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idUsuarioModifico, string? tipoModificacion,
int? idTipoPagoOriginal);
}
}

View File

@@ -41,7 +41,7 @@ namespace GestionIntegral.Api.Data.Repositories.Contables
if (idDestino.HasValue) { sqlBuilder.Append(" AND Id_Destino = @IdDestinoParam"); parameters.Add("IdDestinoParam", idDestino.Value); }
if (idEmpresa.HasValue) { sqlBuilder.Append(" AND Id_Empresa = @IdEmpresaParam"); parameters.Add("IdEmpresaParam", idEmpresa.Value); }
if (!string.IsNullOrWhiteSpace(tipoNota)) { sqlBuilder.Append(" AND Tipo = @TipoParam"); parameters.Add("TipoParam", tipoNota); }
sqlBuilder.Append(" ORDER BY Fecha DESC, Id_Nota DESC;");
try
@@ -86,11 +86,20 @@ namespace GestionIntegral.Api.Data.Repositories.Contables
var inserted = await transaction.Connection!.QuerySingleAsync<NotaCreditoDebito>(sqlInsert, nuevaNota, transaction);
if (inserted == null || inserted.IdNota == 0) throw new DataException("Error al crear la nota o ID no generado.");
await transaction.Connection!.ExecuteAsync(sqlHistorico, new {
IdNotaHist = inserted.IdNota, DestinoHist = inserted.Destino, IdDestinoHist = inserted.IdDestino,
ReferenciaHist = inserted.Referencia, TipoHist = inserted.Tipo, FechaHist = inserted.Fecha, MontoHist = inserted.Monto,
ObservacionesHist = inserted.Observaciones, IdEmpresaHist = inserted.IdEmpresa,
IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Creada"
await transaction.Connection!.ExecuteAsync(sqlHistorico, new
{
IdNotaHist = inserted.IdNota,
DestinoHist = inserted.Destino,
IdDestinoHist = inserted.IdDestino,
ReferenciaHist = inserted.Referencia,
TipoHist = inserted.Tipo,
FechaHist = inserted.Fecha,
MontoHist = inserted.Monto,
ObservacionesHist = inserted.Observaciones,
IdEmpresaHist = inserted.IdEmpresa,
IdUsuarioHist = idUsuario,
FechaModHist = DateTime.Now,
TipoModHist = "Creada"
}, transaction);
return inserted;
}
@@ -107,23 +116,33 @@ namespace GestionIntegral.Api.Data.Repositories.Contables
UPDATE dbo.cue_CreditosDebitos SET
Monto = @Monto, Observaciones = @Observaciones
WHERE Id_Nota = @IdNota;";
const string sqlHistorico = @"
const string sqlHistorico = @"
INSERT INTO dbo.cue_CreditosDebitos_H
(Id_Nota, Destino, Id_Destino, Referencia, Tipo, Fecha, Monto, Observaciones, Id_Empresa, Id_Usuario, FechaMod, TipoMod)
VALUES (@IdNotaHist, @DestinoHist, @IdDestinoHist, @ReferenciaHist, @TipoHist, @FechaHist, @MontoHist, @ObservacionesHist, @IdEmpresaHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
await transaction.Connection!.ExecuteAsync(sqlHistorico, new {
IdNotaHist = actual.IdNota, DestinoHist = actual.Destino, IdDestinoHist = actual.IdDestino, ReferenciaHist = actual.Referencia,
TipoHist = actual.Tipo, FechaHist = actual.Fecha, MontoHist = actual.Monto, // Valor ANTERIOR
ObservacionesHist = actual.Observaciones, IdEmpresaHist = actual.IdEmpresa, // Valor ANTERIOR
IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Actualizada"
await transaction.Connection!.ExecuteAsync(sqlHistorico, new
{
IdNotaHist = actual.IdNota,
DestinoHist = actual.Destino,
IdDestinoHist = actual.IdDestino,
ReferenciaHist = actual.Referencia,
TipoHist = actual.Tipo,
FechaHist = actual.Fecha,
MontoHist = actual.Monto, // Valor ANTERIOR
ObservacionesHist = actual.Observaciones,
IdEmpresaHist = actual.IdEmpresa, // Valor ANTERIOR
IdUsuarioHist = idUsuario,
FechaModHist = DateTime.Now,
TipoModHist = "Actualizada"
}, transaction);
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlUpdate, new {
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlUpdate, new
{
notaAActualizar.Monto,
notaAActualizar.Observaciones,
notaAActualizar.IdNota
} , transaction);
}, transaction);
return rowsAffected == 1;
}
@@ -140,14 +159,67 @@ namespace GestionIntegral.Api.Data.Repositories.Contables
(Id_Nota, Destino, Id_Destino, Referencia, Tipo, Fecha, Monto, Observaciones, Id_Empresa, Id_Usuario, FechaMod, TipoMod)
VALUES (@IdNotaHist, @DestinoHist, @IdDestinoHist, @ReferenciaHist, @TipoHist, @FechaHist, @MontoHist, @ObservacionesHist, @IdEmpresaHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
await transaction.Connection!.ExecuteAsync(sqlHistorico, new {
IdNotaHist = actual.IdNota, DestinoHist = actual.Destino, IdDestinoHist = actual.IdDestino, ReferenciaHist = actual.Referencia,
TipoHist = actual.Tipo, FechaHist = actual.Fecha, MontoHist = actual.Monto, ObservacionesHist = actual.Observaciones, IdEmpresaHist = actual.IdEmpresa,
IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Eliminada"
await transaction.Connection!.ExecuteAsync(sqlHistorico, new
{
IdNotaHist = actual.IdNota,
DestinoHist = actual.Destino,
IdDestinoHist = actual.IdDestino,
ReferenciaHist = actual.Referencia,
TipoHist = actual.Tipo,
FechaHist = actual.Fecha,
MontoHist = actual.Monto,
ObservacionesHist = actual.Observaciones,
IdEmpresaHist = actual.IdEmpresa,
IdUsuarioHist = idUsuario,
FechaModHist = DateTime.Now,
TipoModHist = "Eliminada"
}, transaction);
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlDelete, new { IdNotaParam = idNota }, transaction);
return rowsAffected == 1;
}
public async Task<IEnumerable<(NotaCreditoDebitoHistorico Historial, string NombreUsuarioModifico)>> GetHistorialAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idUsuarioModifico, string? tipoModificacion,
int? idNotaOriginal)
{
using var connection = _cf.CreateConnection();
var sqlBuilder = new StringBuilder(@"
SELECT
h.Id_Nota, h.Destino, h.Id_Destino, h.Referencia, h.Tipo, h.Fecha, h.Monto,
h.Observaciones, h.Id_Empresa,
h.Id_Usuario, h.FechaMod, h.TipoMod,
u.Nombre + ' ' + u.Apellido AS NombreUsuarioModifico
FROM dbo.cue_CreditosDebitos_H h
JOIN dbo.gral_Usuarios u ON h.Id_Usuario = u.Id
WHERE 1=1");
var parameters = new DynamicParameters();
if (fechaDesde.HasValue) { sqlBuilder.Append(" AND h.FechaMod >= @FechaDesdeParam"); parameters.Add("FechaDesdeParam", fechaDesde.Value.Date); }
if (fechaHasta.HasValue) { sqlBuilder.Append(" AND h.FechaMod <= @FechaHastaParam"); parameters.Add("FechaHastaParam", fechaHasta.Value.Date.AddDays(1).AddTicks(-1)); }
if (idUsuarioModifico.HasValue) { sqlBuilder.Append(" AND h.Id_Usuario = @IdUsuarioModificoParam"); parameters.Add("IdUsuarioModificoParam", idUsuarioModifico.Value); }
if (!string.IsNullOrWhiteSpace(tipoModificacion)) { sqlBuilder.Append(" AND h.TipoMod = @TipoModParam"); parameters.Add("TipoModParam", tipoModificacion); }
if (idNotaOriginal.HasValue) { sqlBuilder.Append(" AND h.Id_Nota = @IdNotaOriginalParam"); parameters.Add("IdNotaOriginalParam", idNotaOriginal.Value); }
sqlBuilder.Append(" ORDER BY h.FechaMod DESC;");
try
{
var result = await connection.QueryAsync<NotaCreditoDebitoHistorico, string, (NotaCreditoDebitoHistorico, string)>(
sqlBuilder.ToString(),
(hist, userName) => (hist, userName),
parameters,
splitOn: "NombreUsuarioModifico"
);
return result;
}
catch (Exception ex)
{
_log.LogError(ex, "Error al obtener historial de Notas C/D.");
return Enumerable.Empty<(NotaCreditoDebitoHistorico, string)>();
}
}
}
}

View File

@@ -40,7 +40,7 @@ namespace GestionIntegral.Api.Data.Repositories.Contables
if (idDistribuidor.HasValue) { sqlBuilder.Append(" AND Id_Distribuidor = @IdDistribuidorParam"); parameters.Add("IdDistribuidorParam", idDistribuidor.Value); }
if (idEmpresa.HasValue) { sqlBuilder.Append(" AND Id_Empresa = @IdEmpresaParam"); parameters.Add("IdEmpresaParam", idEmpresa.Value); }
if (!string.IsNullOrWhiteSpace(tipoMovimiento)) { sqlBuilder.Append(" AND TipoMovimiento = @TipoMovParam"); parameters.Add("TipoMovParam", tipoMovimiento); }
sqlBuilder.Append(" ORDER BY Fecha DESC, Id_Pago DESC;");
try
@@ -69,7 +69,7 @@ namespace GestionIntegral.Api.Data.Repositories.Contables
return null;
}
}
public async Task<bool> ExistsByReciboAndTipoMovimientoAsync(int recibo, string tipoMovimiento, int? excludeIdPago = null)
{
var sqlBuilder = new StringBuilder("SELECT COUNT(1) FROM dbo.cue_PagosDistribuidor WHERE Recibo = @ReciboParam AND TipoMovimiento = @TipoMovParam");
@@ -109,11 +109,20 @@ namespace GestionIntegral.Api.Data.Repositories.Contables
var inserted = await transaction.Connection!.QuerySingleAsync<PagoDistribuidor>(sqlInsert, nuevoPago, transaction);
if (inserted == null || inserted.IdPago == 0) throw new DataException("Error al crear pago o ID no generado.");
await transaction.Connection!.ExecuteAsync(sqlHistorico, new {
IdPagoHist = inserted.IdPago, IdDistribuidorHist = inserted.IdDistribuidor, FechaHist = inserted.Fecha,
TipoMovimientoHist = inserted.TipoMovimiento, ReciboHist = inserted.Recibo, MontoHist = inserted.Monto,
IdTipoPagoHist = inserted.IdTipoPago, DetalleHist = inserted.Detalle, IdEmpresaHist = inserted.IdEmpresa,
IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Creado"
await transaction.Connection!.ExecuteAsync(sqlHistorico, new
{
IdPagoHist = inserted.IdPago,
IdDistribuidorHist = inserted.IdDistribuidor,
FechaHist = inserted.Fecha,
TipoMovimientoHist = inserted.TipoMovimiento,
ReciboHist = inserted.Recibo,
MontoHist = inserted.Monto,
IdTipoPagoHist = inserted.IdTipoPago,
DetalleHist = inserted.Detalle,
IdEmpresaHist = inserted.IdEmpresa,
IdUsuarioHist = idUsuario,
FechaModHist = DateTime.Now,
TipoModHist = "Creado"
}, transaction);
return inserted;
}
@@ -136,12 +145,21 @@ namespace GestionIntegral.Api.Data.Repositories.Contables
INSERT INTO dbo.cue_PagosDistribuidor_H
(Id_Pago, Id_Distribuidor, Fecha, TipoMovimiento, Recibo, Monto, Id_TipoPago, Detalle, Id_Empresa, Id_Usuario, FechaMod, TipoMod)
VALUES (@IdPagoHist, @IdDistribuidorHist, @FechaHist, @TipoMovimientoHist, @ReciboHist, @MontoHist, @IdTipoPagoHist, @DetalleHist, @IdEmpresaHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
await transaction.Connection!.ExecuteAsync(sqlHistorico, new {
IdPagoHist = actual.IdPago, IdDistribuidorHist = actual.IdDistribuidor, FechaHist = actual.Fecha,
TipoMovimientoHist = actual.TipoMovimiento, ReciboHist = actual.Recibo, MontoHist = actual.Monto, // Valor ANTERIOR
IdTipoPagoHist = actual.IdTipoPago, DetalleHist = actual.Detalle, IdEmpresaHist = actual.IdEmpresa, // Valores ANTERIORES
IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Actualizado"
await transaction.Connection!.ExecuteAsync(sqlHistorico, new
{
IdPagoHist = actual.IdPago,
IdDistribuidorHist = actual.IdDistribuidor,
FechaHist = actual.Fecha,
TipoMovimientoHist = actual.TipoMovimiento,
ReciboHist = actual.Recibo,
MontoHist = actual.Monto, // Valor ANTERIOR
IdTipoPagoHist = actual.IdTipoPago,
DetalleHist = actual.Detalle,
IdEmpresaHist = actual.IdEmpresa, // Valores ANTERIORES
IdUsuarioHist = idUsuario,
FechaModHist = DateTime.Now,
TipoModHist = "Actualizado"
}, transaction);
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlUpdate, pagoAActualizar, transaction);
@@ -161,15 +179,67 @@ namespace GestionIntegral.Api.Data.Repositories.Contables
(Id_Pago, Id_Distribuidor, Fecha, TipoMovimiento, Recibo, Monto, Id_TipoPago, Detalle, Id_Empresa, Id_Usuario, FechaMod, TipoMod)
VALUES (@IdPagoHist, @IdDistribuidorHist, @FechaHist, @TipoMovimientoHist, @ReciboHist, @MontoHist, @IdTipoPagoHist, @DetalleHist, @IdEmpresaHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
await transaction.Connection!.ExecuteAsync(sqlHistorico, new {
IdPagoHist = actual.IdPago, IdDistribuidorHist = actual.IdDistribuidor, FechaHist = actual.Fecha,
TipoMovimientoHist = actual.TipoMovimiento, ReciboHist = actual.Recibo, MontoHist = actual.Monto,
IdTipoPagoHist = actual.IdTipoPago, DetalleHist = actual.Detalle, IdEmpresaHist = actual.IdEmpresa,
IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Eliminado"
await transaction.Connection!.ExecuteAsync(sqlHistorico, new
{
IdPagoHist = actual.IdPago,
IdDistribuidorHist = actual.IdDistribuidor,
FechaHist = actual.Fecha,
TipoMovimientoHist = actual.TipoMovimiento,
ReciboHist = actual.Recibo,
MontoHist = actual.Monto,
IdTipoPagoHist = actual.IdTipoPago,
DetalleHist = actual.Detalle,
IdEmpresaHist = actual.IdEmpresa,
IdUsuarioHist = idUsuario,
FechaModHist = DateTime.Now,
TipoModHist = "Eliminado"
}, transaction);
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlDelete, new { IdPagoParam = idPago }, transaction);
return rowsAffected == 1;
}
public async Task<IEnumerable<(PagoDistribuidorHistorico Historial, string NombreUsuarioModifico)>> GetHistorialAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idUsuarioModifico, string? tipoModificacion,
int? idPagoOriginal)
{
using var connection = _cf.CreateConnection();
var sqlBuilder = new StringBuilder(@"
SELECT
h.Id_Pago, h.Id_Distribuidor, h.Fecha, h.TipoMovimiento, h.Recibo, h.Monto,
h.Id_TipoPago, h.Detalle, h.Id_Empresa,
h.Id_Usuario, h.FechaMod, h.TipoMod,
u.Nombre + ' ' + u.Apellido AS NombreUsuarioModifico
FROM dbo.cue_PagosDistribuidor_H h
JOIN dbo.gral_Usuarios u ON h.Id_Usuario = u.Id
WHERE 1=1");
var parameters = new DynamicParameters();
if (fechaDesde.HasValue) { sqlBuilder.Append(" AND h.FechaMod >= @FechaDesdeParam"); parameters.Add("FechaDesdeParam", fechaDesde.Value.Date); }
if (fechaHasta.HasValue) { sqlBuilder.Append(" AND h.FechaMod <= @FechaHastaParam"); parameters.Add("FechaHastaParam", fechaHasta.Value.Date.AddDays(1).AddTicks(-1)); }
if (idUsuarioModifico.HasValue) { sqlBuilder.Append(" AND h.Id_Usuario = @IdUsuarioModificoParam"); parameters.Add("IdUsuarioModificoParam", idUsuarioModifico.Value); }
if (!string.IsNullOrWhiteSpace(tipoModificacion)) { sqlBuilder.Append(" AND h.TipoMod = @TipoModParam"); parameters.Add("TipoModParam", tipoModificacion); }
if (idPagoOriginal.HasValue) { sqlBuilder.Append(" AND h.Id_Pago = @IdPagoOriginalParam"); parameters.Add("IdPagoOriginalParam", idPagoOriginal.Value); }
sqlBuilder.Append(" ORDER BY h.FechaMod DESC;");
try
{
var result = await connection.QueryAsync<PagoDistribuidorHistorico, string, (PagoDistribuidorHistorico, string)>(
sqlBuilder.ToString(),
(hist, userName) => (hist, userName),
parameters,
splitOn: "NombreUsuarioModifico"
);
return result;
}
catch (Exception ex)
{
_log.LogError(ex, "Error al obtener historial de Pagos de Distribuidores.");
return Enumerable.Empty<(PagoDistribuidorHistorico, string)>();
}
}
}
}

View File

@@ -1,10 +1,12 @@
using Dapper;
using GestionIntegral.Api.Data.Repositories;
using GestionIntegral.Api.Data.Repositories;
using GestionIntegral.Api.Models;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Data;
using System.Threading.Tasks;
using GestionIntegral.Api.Models.Contables;
using System.Text;
namespace GestionIntegral.Api.Data.Repositories.Contables
{
@@ -57,61 +59,64 @@ namespace GestionIntegral.Api.Data.Repositories.Contables
public async Task<bool> DeleteSaldosByEmpresaAsync(int idEmpresa, IDbTransaction transaction)
{
var sql = "DELETE FROM dbo.cue_Saldos WHERE Id_Empresa = @IdEmpresa";
try
{
await transaction.Connection!.ExecuteAsync(sql, new { IdEmpresa = idEmpresa }, transaction: transaction);
return true; // Asumir éxito si no hay excepción
}
catch (Exception ex)
{
_logger.LogError(ex, "Error al eliminar saldos para Empresa ID {IdEmpresa}.", idEmpresa);
throw;
}
var sql = "DELETE FROM dbo.cue_Saldos WHERE Id_Empresa = @IdEmpresa";
try
{
await transaction.Connection!.ExecuteAsync(sql, new { IdEmpresa = idEmpresa }, transaction: transaction);
return true; // Asumir éxito si no hay excepción
}
catch (Exception ex)
{
_logger.LogError(ex, "Error al eliminar saldos para Empresa ID {IdEmpresa}.", idEmpresa);
throw;
}
}
public async Task<bool> ModificarSaldoAsync(string destino, int idDestino, int idEmpresa, decimal montoAAgregar, IDbTransaction? transaction = null)
{
var sql = @"UPDATE dbo.cue_Saldos
SET Monto = Monto + @MontoAAgregar
WHERE Destino = @Destino AND Id_Destino = @IdDestino AND Id_Empresa = @IdEmpresa;";
{
var sql = @"UPDATE dbo.cue_Saldos
SET Monto = Monto + @MontoAAgregar,
FechaUltimaModificacion = @FechaActualizacion -- << AÑADIR
WHERE Destino = @Destino AND Id_Destino = @IdDestino AND Id_Empresa = @IdEmpresa;";
// Usar una variable para la conexión para poder aplicar el '!' si es necesario
IDbConnection connection = transaction?.Connection ?? _connectionFactory.CreateConnection();
bool ownConnection = transaction == null; // Saber si necesitamos cerrar la conexión nosotros
IDbConnection connection = transaction?.Connection ?? _connectionFactory.CreateConnection();
bool ownConnection = transaction == null;
try
{
if (ownConnection) await (connection as System.Data.Common.DbConnection)!.OpenAsync(); // Abrir solo si no hay transacción externa
try
{
if (ownConnection && connection.State != ConnectionState.Open)
{
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
}
var parameters = new {
MontoAAgregar = montoAAgregar,
Destino = destino,
IdDestino = idDestino,
IdEmpresa = idEmpresa
};
// Aplicar '!' aquí también si viene de la transacción
int rowsAffected = await connection.ExecuteAsync(sql, parameters, transaction: transaction);
return rowsAffected == 1;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error al modificar saldo para {Destino} ID {IdDestino}, Empresa ID {IdEmpresa}.", destino, idDestino, idEmpresa);
if (transaction != null) throw; // Re-lanzar si estamos en una transacción externa
return false; // Devolver false si fue una operación aislada que falló
}
finally
{
// Cerrar la conexión solo si la abrimos nosotros (no había transacción externa)
if (ownConnection && connection.State == ConnectionState.Open)
{
await (connection as System.Data.Common.DbConnection)!.CloseAsync();
}
// Disponer de la conexión si la creamos nosotros
if(ownConnection) (connection as IDisposable)?.Dispose();
}
}
public async Task<bool> CheckIfSaldosExistForEmpresaAsync(int idEmpresa)
var parameters = new
{
MontoAAgregar = montoAAgregar,
Destino = destino,
IdDestino = idDestino,
IdEmpresa = idEmpresa,
FechaActualizacion = DateTime.Now // O DateTime.UtcNow si prefieres
};
int rowsAffected = await connection.ExecuteAsync(sql, parameters, transaction: transaction);
return rowsAffected == 1;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error al modificar saldo para {Destino} ID {IdDestino}, Empresa ID {IdEmpresa}.", destino, idDestino, idEmpresa);
if (transaction != null) throw;
return false;
}
finally
{
if (ownConnection && connection.State == ConnectionState.Open)
{
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.CloseAsync(); else connection.Close();
}
if (ownConnection && connection is IDisposable d) d.Dispose(); // Mejorar dispose
}
}
public async Task<bool> CheckIfSaldosExistForEmpresaAsync(int idEmpresa)
{
var sql = "SELECT COUNT(1) FROM dbo.cue_Saldos WHERE Id_Empresa = @IdEmpresa";
try
@@ -130,5 +135,102 @@ namespace GestionIntegral.Api.Data.Repositories.Contables
// O podrías devolver true para ser más conservador si la verificación es crítica.
}
}
public async Task<IEnumerable<Saldo>> GetSaldosParaGestionAsync(string? destinoFilter, int? idDestinoFilter, int? idEmpresaFilter)
{
var sqlBuilder = new StringBuilder("SELECT Id_Saldo AS IdSaldo, Destino, Id_Destino AS IdDestino, Monto, Id_Empresa AS IdEmpresa, FechaUltimaModificacion FROM dbo.cue_Saldos WHERE 1=1");
var parameters = new DynamicParameters();
if (!string.IsNullOrWhiteSpace(destinoFilter))
{
sqlBuilder.Append(" AND Destino = @Destino");
parameters.Add("Destino", destinoFilter);
}
if (idDestinoFilter.HasValue)
{
sqlBuilder.Append(" AND Id_Destino = @IdDestino");
parameters.Add("IdDestino", idDestinoFilter.Value);
}
if (idEmpresaFilter.HasValue)
{
sqlBuilder.Append(" AND Id_Empresa = @IdEmpresa");
parameters.Add("IdEmpresa", idEmpresaFilter.Value);
}
sqlBuilder.Append(" ORDER BY Destino, Id_Empresa, Id_Destino;");
using var connection = _connectionFactory.CreateConnection();
return await connection.QueryAsync<Saldo>(sqlBuilder.ToString(), parameters);
}
public async Task<Saldo?> GetSaldoAsync(string destino, int idDestino, int idEmpresa, IDbTransaction? transaction = null)
{
const string sql = "SELECT Id_Saldo AS IdSaldo, Destino, Id_Destino AS IdDestino, Monto, Id_Empresa AS IdEmpresa, FechaUltimaModificacion FROM dbo.cue_Saldos WHERE Destino = @Destino AND Id_Destino = @IdDestino AND Id_Empresa = @IdEmpresa;";
var conn = transaction?.Connection ?? _connectionFactory.CreateConnection();
if (transaction == null && conn.State != ConnectionState.Open) { if (conn is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else conn.Open(); }
try
{
return await conn.QuerySingleOrDefaultAsync<Saldo>(sql, new { Destino = destino, IdDestino = idDestino, IdEmpresa = idEmpresa }, transaction);
}
finally
{
if (transaction == null && conn.State == ConnectionState.Open) { if (conn is System.Data.Common.DbConnection dbConn) await dbConn.CloseAsync(); else conn.Close(); }
}
}
public async Task CreateSaldoAjusteHistorialAsync(SaldoAjusteHistorial historialEntry, IDbTransaction transaction)
{
const string sql = @"
INSERT INTO dbo.cue_SaldoAjustesHistorial
(Destino, Id_Destino, Id_Empresa, MontoAjuste, SaldoAnterior, SaldoNuevo, Justificacion, FechaAjuste, Id_UsuarioAjuste)
VALUES
(@Destino, @IdDestino, @IdEmpresa, @MontoAjuste, @SaldoAnterior, @SaldoNuevo, @Justificacion, @FechaAjuste, @IdUsuarioAjuste);";
await transaction.Connection!.ExecuteAsync(sql, historialEntry, transaction);
}
public async Task<IEnumerable<(SaldoAjusteHistorial Historial, string NombreUsuarioModifico)>> GetHistorialAjustesAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idUsuarioModifico,
string? destino, int? idDestino, int? idEmpresa)
{
using var connection = _connectionFactory.CreateConnection();
var sqlBuilder = new StringBuilder(@"
SELECT
h.IdSaldoAjusteHist, h.Destino, h.Id_Destino, h.Id_Empresa,
h.MontoAjuste, h.SaldoAnterior, h.SaldoNuevo, h.Justificacion,
h.FechaAjuste, h.Id_UsuarioAjuste,
u.Nombre + ' ' + u.Apellido AS NombreUsuarioModifico
FROM dbo.cue_SaldoAjustesHistorial h
JOIN dbo.gral_Usuarios u ON h.Id_UsuarioAjuste = u.Id
WHERE 1=1");
var parameters = new DynamicParameters();
if (fechaDesde.HasValue) { sqlBuilder.Append(" AND h.FechaAjuste >= @FechaDesdeParam"); parameters.Add("FechaDesdeParam", fechaDesde.Value.Date); }
if (fechaHasta.HasValue) { sqlBuilder.Append(" AND h.FechaAjuste <= @FechaHastaParam"); parameters.Add("FechaHastaParam", fechaHasta.Value.Date.AddDays(1).AddTicks(-1)); }
if (idUsuarioModifico.HasValue) { sqlBuilder.Append(" AND h.Id_UsuarioAjuste = @IdUsuarioModificoParam"); parameters.Add("IdUsuarioModificoParam", idUsuarioModifico.Value); }
if (!string.IsNullOrWhiteSpace(destino)) { sqlBuilder.Append(" AND h.Destino = @DestinoParam"); parameters.Add("DestinoParam", destino); }
if (idDestino.HasValue) { sqlBuilder.Append(" AND h.Id_Destino = @IdDestinoParam"); parameters.Add("IdDestinoParam", idDestino.Value); }
if (idEmpresa.HasValue) { sqlBuilder.Append(" AND h.Id_Empresa = @IdEmpresaParam"); parameters.Add("IdEmpresaParam", idEmpresa.Value); }
sqlBuilder.Append(" ORDER BY h.FechaAjuste DESC;");
try
{
var result = await connection.QueryAsync<SaldoAjusteHistorial, string, (SaldoAjusteHistorial, string)>(
sqlBuilder.ToString(),
(hist, userName) => (hist, userName),
parameters,
splitOn: "NombreUsuarioModifico"
);
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error al obtener historial de Ajustes de Saldo.");
return Enumerable.Empty<(SaldoAjusteHistorial, string)>();
}
}
}
}

View File

@@ -2,6 +2,7 @@ using Dapper;
using GestionIntegral.Api.Models.Contables;
using System.Collections.Generic;
using System.Data;
using System.Text;
using System.Threading.Tasks;
namespace GestionIntegral.Api.Data.Repositories.Contables
@@ -284,5 +285,47 @@ namespace GestionIntegral.Api.Data.Repositories.Contables
return true; // Asumir que está en uso si hay error para prevenir borrado incorrecto
}
}
public async Task<IEnumerable<(TipoPagoHistorico Historial, string NombreUsuarioModifico)>> GetHistorialAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idUsuarioModifico, string? tipoModificacion,
int? idTipoPagoOriginal)
{
using var connection = _connectionFactory.CreateConnection(); // Asumiendo _cf es tu DbConnectionFactory
var sqlBuilder = new StringBuilder(@"
SELECT
h.Id_TipoPago, h.Nombre, h.Detalle,
h.Id_Usuario, h.FechaMod, h.TipoMod,
u.Nombre + ' ' + u.Apellido AS NombreUsuarioModifico
FROM dbo.cue_dtTipopago_H h
JOIN dbo.gral_Usuarios u ON h.Id_Usuario = u.Id
WHERE 1=1");
var parameters = new DynamicParameters();
if (fechaDesde.HasValue) { sqlBuilder.Append(" AND h.FechaMod >= @FechaDesdeParam"); parameters.Add("FechaDesdeParam", fechaDesde.Value.Date); }
if (fechaHasta.HasValue) { sqlBuilder.Append(" AND h.FechaMod <= @FechaHastaParam"); parameters.Add("FechaHastaParam", fechaHasta.Value.Date.AddDays(1).AddTicks(-1)); }
if (idUsuarioModifico.HasValue) { sqlBuilder.Append(" AND h.Id_Usuario = @IdUsuarioModificoParam"); parameters.Add("IdUsuarioModificoParam", idUsuarioModifico.Value); }
if (!string.IsNullOrWhiteSpace(tipoModificacion)) { sqlBuilder.Append(" AND h.TipoMod = @TipoModParam"); parameters.Add("TipoModParam", tipoModificacion); }
if (idTipoPagoOriginal.HasValue) { sqlBuilder.Append(" AND h.Id_TipoPago = @IdTipoPagoOriginalParam"); parameters.Add("IdTipoPagoOriginalParam", idTipoPagoOriginal.Value); }
sqlBuilder.Append(" ORDER BY h.FechaMod DESC;");
try
{
var result = await connection.QueryAsync<TipoPagoHistorico, string, (TipoPagoHistorico, string)>(
sqlBuilder.ToString(),
(hist, userName) => (hist, userName),
parameters,
splitOn: "NombreUsuarioModifico"
);
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error al obtener historial de Tipos de Pago."); // Asumiendo _log
return Enumerable.Empty<(TipoPagoHistorico, string)>();
}
}
}
}

View File

@@ -0,0 +1,193 @@
using Dapper;
using GestionIntegral.Api.Models.Distribucion;
using Microsoft.Extensions.Logging; // Para ILogger
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text; // Para StringBuilder
using System.Threading.Tasks;
namespace GestionIntegral.Api.Data.Repositories.Distribucion
{
public class CambioParadaRepository : ICambioParadaRepository
{
private readonly DbConnectionFactory _cf;
private readonly ILogger<CambioParadaRepository> _logger;
public CambioParadaRepository(DbConnectionFactory cf, ILogger<CambioParadaRepository> logger)
{
_cf = cf;
_logger = logger;
}
private async Task LogHistorialAsync(CambioParadaCanilla paradaOriginal, int idUsuario, string tipoMod, IDbConnection connection, IDbTransaction? transaction)
{
var historial = new CambioParadaCanillaHistorial
{
Id_Registro = paradaOriginal.IdRegistro,
Id_Canilla = paradaOriginal.IdCanilla,
Parada = paradaOriginal.Parada,
VigenciaD = paradaOriginal.VigenciaD,
VigenciaH = paradaOriginal.VigenciaH,
Id_Usuario = idUsuario,
FechaMod = DateTime.Now,
TipoMod = tipoMod
};
const string sqlHistorial = @"
INSERT INTO dbo.dist_CambiosParadasCanillas_H
(Id_Registro, Id_Canilla, Parada, VigenciaD, VigenciaH, Id_Usuario, FechaMod, TipoMod)
VALUES
(@Id_Registro, @Id_Canilla, @Parada, @VigenciaD, @VigenciaH, @Id_Usuario, @FechaMod, @TipoMod);";
await connection.ExecuteAsync(sqlHistorial, historial, transaction);
}
public async Task<IEnumerable<CambioParadaCanilla>> GetByCanillaAsync(int idCanilla)
{
using var connection = _cf.CreateConnection();
const string sql = @"
SELECT Id_Registro AS IdRegistro, Id_Canilla AS IdCanilla, Parada, VigenciaD, VigenciaH
FROM dbo.dist_CambiosParadasCanillas
WHERE Id_Canilla = @IdCanilla
ORDER BY VigenciaD DESC, Id_Registro DESC;";
return await connection.QueryAsync<CambioParadaCanilla>(sql, new { IdCanilla = idCanilla });
}
public async Task<CambioParadaCanilla?> GetByIdAsync(int idRegistro)
{
using var connection = _cf.CreateConnection();
const string sql = @"
SELECT Id_Registro AS IdRegistro, Id_Canilla AS IdCanilla, Parada, VigenciaD, VigenciaH
FROM dbo.dist_CambiosParadasCanillas
WHERE Id_Registro = @IdRegistro;";
return await connection.QuerySingleOrDefaultAsync<CambioParadaCanilla>(sql, new { IdRegistro = idRegistro });
}
public async Task<CambioParadaCanilla?> GetCurrentParadaAsync(int idCanilla, IDbTransaction? transaction = null)
{
const string sql = @"
SELECT TOP 1 Id_Registro AS IdRegistro, Id_Canilla AS IdCanilla, Parada, VigenciaD, VigenciaH
FROM dbo.dist_CambiosParadasCanillas
WHERE Id_Canilla = @IdCanilla AND VigenciaH IS NULL
ORDER BY VigenciaD DESC, Id_Registro DESC;"; // Por si hay un error y quedaron varias abiertas
var conn = transaction?.Connection ?? _cf.CreateConnection();
bool manageConnection = transaction == null;
if (manageConnection && conn.State != ConnectionState.Open) { if (conn is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else conn.Open(); }
try
{
return await conn.QuerySingleOrDefaultAsync<CambioParadaCanilla>(sql, new { IdCanilla = idCanilla }, transaction);
}
finally
{
if (manageConnection && conn.State == ConnectionState.Open) { if (conn is System.Data.Common.DbConnection dbConn) await dbConn.CloseAsync(); else conn.Close(); }
}
}
public async Task<CambioParadaCanilla?> CreateAsync(CambioParadaCanilla nuevaParada, int idUsuario, IDbTransaction transaction)
{
const string sqlInsert = @"
INSERT INTO dbo.dist_CambiosParadasCanillas (Id_Canilla, Parada, VigenciaD, VigenciaH)
OUTPUT INSERTED.Id_Registro AS IdRegistro, INSERTED.Id_Canilla AS IdCanilla, INSERTED.Parada, INSERTED.VigenciaD, INSERTED.VigenciaH
VALUES (@IdCanilla, @Parada, @VigenciaD, @VigenciaH);";
// VigenciaH es null al crear una nueva parada activa
var inserted = await transaction.Connection!.QuerySingleAsync<CambioParadaCanilla>(sqlInsert,
new { nuevaParada.IdCanilla, nuevaParada.Parada, nuevaParada.VigenciaD, VigenciaH = (DateTime?)null },
transaction);
if (inserted == null || inserted.IdRegistro == 0) throw new DataException("Error al crear cambio de parada o ID no generado.");
await LogHistorialAsync(inserted, idUsuario, "Creada", transaction.Connection!, transaction);
return inserted;
}
public async Task<bool> UpdateVigenciaHAsync(int idRegistro, DateTime vigenciaH, int idUsuario, IDbTransaction transaction)
{
var paradaOriginal = await GetByIdAsync(idRegistro);
if (paradaOriginal == null) throw new KeyNotFoundException("Registro de parada no encontrado para actualizar VigenciaH.");
var paradaParaHistorial = new CambioParadaCanilla
{
IdRegistro = paradaOriginal.IdRegistro,
IdCanilla = paradaOriginal.IdCanilla,
Parada = paradaOriginal.Parada,
VigenciaD = paradaOriginal.VigenciaD,
VigenciaH = vigenciaH.Date
};
// Loggear el estado que QUEDARÁ en la tabla principal (con la VigenciaH actualizada)
// El TipoMod debería reflejar la acción. "Cerrada".
await LogHistorialAsync(paradaParaHistorial, idUsuario, "Cerrada", transaction.Connection!, transaction);
const string sqlUpdate = @"
UPDATE dbo.dist_CambiosParadasCanillas
SET VigenciaH = @VigenciaH
WHERE Id_Registro = @IdRegistro;";
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlUpdate,
new { VigenciaH = vigenciaH.Date, IdRegistro = idRegistro },
transaction);
return rowsAffected == 1;
}
public async Task<bool> DeleteAsync(int idRegistro, int idUsuario, IDbTransaction transaction)
{
var paradaOriginal = await GetByIdAsync(idRegistro);
if (paradaOriginal == null) throw new KeyNotFoundException("Registro de parada no encontrado para eliminar.");
// Loggear ANTES de eliminar
await LogHistorialAsync(paradaOriginal, idUsuario, "Eliminada", transaction.Connection!, transaction);
const string sqlDelete = "DELETE FROM dbo.dist_CambiosParadasCanillas WHERE Id_Registro = @IdRegistro;";
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlDelete, new { IdRegistro = idRegistro }, transaction);
return rowsAffected == 1;
}
public async Task<IEnumerable<(CambioParadaCanillaHistorial Historial, string NombreUsuarioModifico, string NombreCanilla)>> GetCambiosParadaHistorialAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idUsuarioModifico, string? tipoModificacion,
int? idCanillaOriginal)
{
using var connection = _cf.CreateConnection();
var sqlBuilder = new StringBuilder(@"
SELECT
h.Id_Registro, h.Id_Canilla, h.Parada, h.VigenciaD, h.VigenciaH,
h.Id_Usuario, h.FechaMod, h.TipoMod,
u.Nombre + ' ' + u.Apellido AS NombreUsuarioModifico,
c.NomApe AS NombreCanilla
FROM dbo.dist_CambiosParadasCanillas_H h
JOIN dbo.gral_Usuarios u ON h.Id_Usuario = u.Id
JOIN dbo.dist_dtCanillas c ON h.Id_Canilla = c.Id_Canilla
WHERE 1=1");
var parameters = new DynamicParameters();
if (fechaDesde.HasValue) { sqlBuilder.Append(" AND h.FechaMod >= @FechaDesdeParam"); parameters.Add("FechaDesdeParam", fechaDesde.Value.Date); }
if (fechaHasta.HasValue) { sqlBuilder.Append(" AND h.FechaMod <= @FechaHastaParam"); parameters.Add("FechaHastaParam", fechaHasta.Value.Date.AddDays(1).AddTicks(-1)); }
if (idUsuarioModifico.HasValue) { sqlBuilder.Append(" AND h.Id_Usuario = @IdUsuarioModificoParam"); parameters.Add("IdUsuarioModificoParam", idUsuarioModifico.Value); }
if (!string.IsNullOrWhiteSpace(tipoModificacion)) { sqlBuilder.Append(" AND h.TipoMod = @TipoModParam"); parameters.Add("TipoModParam", tipoModificacion); }
if (idCanillaOriginal.HasValue) { sqlBuilder.Append(" AND h.Id_Canilla = @IdCanillaOriginalParam"); parameters.Add("IdCanillaOriginalParam", idCanillaOriginal.Value); }
sqlBuilder.Append(" ORDER BY h.FechaMod DESC;");
try
{
var result = await connection.QueryAsync<CambioParadaCanillaHistorial, string, string, (CambioParadaCanillaHistorial, string, string)>(
sqlBuilder.ToString(),
(hist, userMod, canillaNombre) => (hist, userMod, canillaNombre),
parameters,
splitOn: "NombreUsuarioModifico,NombreCanilla"
);
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error al obtener historial de Cambios de Parada.");
return Enumerable.Empty<(CambioParadaCanillaHistorial, string, string)>();
}
}
}
}

View File

@@ -1,4 +1,5 @@
using Dapper;
using GestionIntegral.Api.Dtos.Distribucion;
using GestionIntegral.Api.Models.Distribucion;
using Microsoft.Extensions.Logging;
using System; // Para Exception
@@ -21,51 +22,87 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
_logger = logger;
}
public async Task<IEnumerable<(Canilla Canilla, string NombreZona, string NombreEmpresa)>> GetAllAsync(string? nomApeFilter, int? legajoFilter, bool? soloActivos)
public async Task<IEnumerable<(Canilla Canilla, string? NombreZona, string? NombreEmpresa)>> GetAllAsync(
string? nomApeFilter,
int? legajoFilter,
bool? esAccionista,
bool? soloActivos)
{
var sqlBuilder = new StringBuilder(@"
SELECT
c.Id_Canilla AS IdCanilla, c.Legajo, c.NomApe, c.Parada, c.Id_Zona AS IdZona,
c.Accionista, c.Obs, c.Empresa, c.Baja, c.FechaBaja,
z.Nombre AS NombreZona,
ISNULL(e.Nombre, 'N/A (Accionista)') AS NombreEmpresa
FROM dbo.dist_dtCanillas c
INNER JOIN dbo.dist_dtZonas z ON c.Id_Zona = z.Id_Zona
LEFT JOIN dbo.dist_dtEmpresas e ON c.Empresa = e.Id_Empresa
WHERE 1=1");
using var connection = _connectionFactory.CreateConnection();
var sqlBuilder = new System.Text.StringBuilder(@"
SELECT c.Id_Canilla AS IdCanilla, c.Legajo, c.NomApe, c.Parada, c.Id_Zona AS IdZona,
c.Accionista, c.Obs, c.Empresa, c.Baja, c.FechaBaja,
z.Nombre AS NombreZona,
e.Nombre AS NombreEmpresa
FROM dbo.dist_dtCanillas c
LEFT JOIN dbo.dist_dtZonas z ON c.Id_Zona = z.Id_Zona
LEFT JOIN dbo.dist_dtEmpresas e ON c.Empresa = e.Id_Empresa
WHERE 1=1 "); // Cláusula base para añadir AND fácilmente
var parameters = new DynamicParameters();
if (soloActivos.HasValue)
{
sqlBuilder.Append(soloActivos.Value ? " AND c.Baja = 0" : " AND c.Baja = 1");
}
if (!string.IsNullOrWhiteSpace(nomApeFilter))
{
sqlBuilder.Append(" AND c.NomApe LIKE @NomApeParam");
parameters.Add("NomApeParam", $"%{nomApeFilter}%");
sqlBuilder.Append(" AND c.NomApe LIKE @NomApeFilter ");
parameters.Add("NomApeFilter", $"%{nomApeFilter}%");
}
if (legajoFilter.HasValue)
{
sqlBuilder.Append(" AND c.Legajo = @LegajoParam");
parameters.Add("LegajoParam", legajoFilter.Value);
sqlBuilder.Append(" AND c.Legajo = @LegajoFilter ");
parameters.Add("LegajoFilter", legajoFilter.Value);
}
if (soloActivos.HasValue)
{
sqlBuilder.Append(" AND c.Baja = @BajaStatus ");
parameters.Add("BajaStatus", !soloActivos.Value); // Si soloActivos es true, Baja debe ser false
}
if (esAccionista.HasValue)
{
sqlBuilder.Append(" AND c.Accionista = @EsAccionista ");
parameters.Add("EsAccionista", esAccionista.Value); // true para accionistas, false para no accionistas (canillitas)
}
sqlBuilder.Append(" ORDER BY c.NomApe;");
try
var result = await connection.QueryAsync<Canilla, string, string, (Canilla, string?, string?)>(
sqlBuilder.ToString(),
(can, zona, emp) => (can, zona, emp),
parameters,
splitOn: "NombreZona,NombreEmpresa"
);
return result;
}
public async Task<IEnumerable<CanillaDropdownDto>> GetAllDropdownAsync(bool? esAccionista, bool? soloActivos)
{
using var connection = _connectionFactory.CreateConnection();
var sqlBuilder = new System.Text.StringBuilder(@"
SELECT c.Id_Canilla AS IdCanilla, c.Legajo, c.NomApe
FROM dbo.dist_dtCanillas c
WHERE 1=1 ");
var parameters = new DynamicParameters();
if (soloActivos.HasValue)
{
using var connection = _connectionFactory.CreateConnection();
return await connection.QueryAsync<Canilla, string, string, (Canilla, string, string)>(
sqlBuilder.ToString(),
(canilla, nombreZona, nombreEmpresa) => (canilla, nombreZona, nombreEmpresa),
parameters,
splitOn: "NombreZona,NombreEmpresa"
);
sqlBuilder.Append(" AND c.Baja = @BajaStatus ");
parameters.Add("BajaStatus", !soloActivos.Value); // Si soloActivos es true, Baja debe ser false
}
catch (Exception ex)
if (esAccionista.HasValue)
{
_logger.LogError(ex, "Error al obtener todos los Canillas. Filtros: NomApe='{NomApeFilter}', Legajo='{LegajoFilter}', SoloActivos='{SoloActivos}'", nomApeFilter, legajoFilter, soloActivos);
return Enumerable.Empty<(Canilla, string, string)>();
sqlBuilder.Append(" AND c.Accionista = @EsAccionista ");
parameters.Add("EsAccionista", esAccionista.Value); // true para accionistas, false para no accionistas (canillitas)
}
sqlBuilder.Append(" ORDER BY c.NomApe;");
var result = await connection.QueryAsync<CanillaDropdownDto>(
sqlBuilder.ToString(),
parameters
);
return result;
}
public async Task<(Canilla? Canilla, string? NombreZona, string? NombreEmpresa)> GetByIdAsync(int id)
@@ -83,12 +120,12 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
try
{
using var connection = _connectionFactory.CreateConnection();
var result = await connection.QueryAsync<Canilla, string, string, (Canilla?, string?, string?)>(
sql,
(canilla, nombreZona, nombreEmpresa) => (canilla, nombreZona, nombreEmpresa),
new { IdParam = id },
splitOn: "NombreZona,NombreEmpresa"
);
var result = await connection.QueryAsync<Canilla, string, string, (Canilla?, string?, string?)>(
sql,
(canilla, nombreZona, nombreEmpresa) => (canilla, nombreZona, nombreEmpresa),
new { IdParam = id },
splitOn: "NombreZona,NombreEmpresa"
);
return result.SingleOrDefault();
}
catch (Exception ex)
@@ -160,9 +197,19 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
await connection.ExecuteAsync(sqlInsertHistorico, new
{
IdCanillaParam = insertedCanilla.IdCanilla, LegajoParam = insertedCanilla.Legajo, NomApeParam = insertedCanilla.NomApe, ParadaParam = insertedCanilla.Parada, IdZonaParam = insertedCanilla.IdZona,
AccionistaParam = insertedCanilla.Accionista, ObsParam = insertedCanilla.Obs, EmpresaParam = insertedCanilla.Empresa, BajaParam = insertedCanilla.Baja, FechaBajaParam = insertedCanilla.FechaBaja,
Id_UsuarioParam = idUsuario, FechaModParam = DateTime.Now, TipoModParam = "Creado"
IdCanillaParam = insertedCanilla.IdCanilla,
LegajoParam = insertedCanilla.Legajo,
NomApeParam = insertedCanilla.NomApe,
ParadaParam = insertedCanilla.Parada,
IdZonaParam = insertedCanilla.IdZona,
AccionistaParam = insertedCanilla.Accionista,
ObsParam = insertedCanilla.Obs,
EmpresaParam = insertedCanilla.Empresa,
BajaParam = insertedCanilla.Baja,
FechaBajaParam = insertedCanilla.FechaBaja,
Id_UsuarioParam = idUsuario,
FechaModParam = DateTime.Now,
TipoModParam = "Creado"
}, transaction);
return insertedCanilla;
}
@@ -173,7 +220,7 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
var canillaActual = await connection.QuerySingleOrDefaultAsync<Canilla>(
@"SELECT Id_Canilla AS IdCanilla, Legajo, NomApe, Parada, Id_Zona AS IdZona,
Accionista, Obs, Empresa, Baja, FechaBaja
FROM dbo.dist_dtCanillas WHERE Id_Canilla = @IdCanillaParam",
FROM dbo.dist_dtCanillas WHERE Id_Canilla = @IdCanillaParam",
new { IdCanillaParam = canillaAActualizar.IdCanilla }, transaction);
if (canillaActual == null) throw new KeyNotFoundException("Canilla no encontrado para actualizar.");
@@ -187,13 +234,21 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
(Id_Canilla, Legajo, NomApe, Parada, Id_Zona, Accionista, Obs, Empresa, Baja, FechaBaja, Id_Usuario, FechaMod, TipoMod)
VALUES (@IdCanillaParam, @LegajoParam, @NomApeParam, @ParadaParam, @IdZonaParam, @AccionistaParam, @ObsParam, @EmpresaParam, @BajaParam, @FechaBajaParam, @Id_UsuarioParam, @FechaModParam, @TipoModParam);";
await connection.ExecuteAsync(sqlInsertHistorico, new
await connection.ExecuteAsync(sqlInsertHistorico, new
{
IdCanillaParam = canillaActual.IdCanilla,
LegajoParam = canillaActual.Legajo, NomApeParam = canillaActual.NomApe, ParadaParam = canillaActual.Parada, IdZonaParam = canillaActual.IdZona,
AccionistaParam = canillaActual.Accionista, ObsParam = canillaActual.Obs, EmpresaParam = canillaActual.Empresa,
BajaParam = canillaActual.Baja, FechaBajaParam = canillaActual.FechaBaja,
Id_UsuarioParam = idUsuario, FechaModParam = DateTime.Now, TipoModParam = "Actualizado"
LegajoParam = canillaActual.Legajo,
NomApeParam = canillaActual.NomApe,
ParadaParam = canillaActual.Parada,
IdZonaParam = canillaActual.IdZona,
AccionistaParam = canillaActual.Accionista,
ObsParam = canillaActual.Obs,
EmpresaParam = canillaActual.Empresa,
BajaParam = canillaActual.Baja,
FechaBajaParam = canillaActual.FechaBaja,
Id_UsuarioParam = idUsuario,
FechaModParam = DateTime.Now,
TipoModParam = "Actualizado"
}, transaction);
var rowsAffected = await connection.ExecuteAsync(sqlUpdate, canillaAActualizar, transaction);
@@ -206,7 +261,7 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
var canillaActual = await connection.QuerySingleOrDefaultAsync<Canilla>(
@"SELECT Id_Canilla AS IdCanilla, Legajo, NomApe, Parada, Id_Zona AS IdZona,
Accionista, Obs, Empresa, Baja, FechaBaja
FROM dbo.dist_dtCanillas WHERE Id_Canilla = @IdCanillaParam",
FROM dbo.dist_dtCanillas WHERE Id_Canilla = @IdCanillaParam",
new { IdCanillaParam = id }, transaction);
if (canillaActual == null) throw new KeyNotFoundException("Canilla no encontrado para dar de baja/alta.");
@@ -218,14 +273,65 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
await connection.ExecuteAsync(sqlInsertHistorico, new
{
IdCanillaParam = canillaActual.IdCanilla, LegajoParam = canillaActual.Legajo, NomApeParam = canillaActual.NomApe, ParadaParam = canillaActual.Parada, IdZonaParam = canillaActual.IdZona,
AccionistaParam = canillaActual.Accionista, ObsParam = canillaActual.Obs, EmpresaParam = canillaActual.Empresa,
BajaNuevaParam = darDeBaja, FechaBajaNuevaParam = (darDeBaja ? fechaBaja : null),
Id_UsuarioParam = idUsuario, FechaModParam = DateTime.Now, TipoModHistParam = (darDeBaja ? "Baja" : "Alta")
IdCanillaParam = canillaActual.IdCanilla,
LegajoParam = canillaActual.Legajo,
NomApeParam = canillaActual.NomApe,
ParadaParam = canillaActual.Parada,
IdZonaParam = canillaActual.IdZona,
AccionistaParam = canillaActual.Accionista,
ObsParam = canillaActual.Obs,
EmpresaParam = canillaActual.Empresa,
BajaNuevaParam = darDeBaja,
FechaBajaNuevaParam = (darDeBaja ? fechaBaja : null),
Id_UsuarioParam = idUsuario,
FechaModParam = DateTime.Now,
TipoModHistParam = (darDeBaja ? "Baja" : "Alta")
}, transaction);
var rowsAffected = await connection.ExecuteAsync(sqlUpdate, new { BajaParam = darDeBaja, FechaBajaParam = (darDeBaja ? fechaBaja : null), IdCanillaParam = id }, transaction);
return rowsAffected == 1;
}
public async Task<IEnumerable<(CanillaHistorico Historial, string NombreUsuarioModifico)>> GetHistorialAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idUsuarioModifico, string? tipoModificacion,
int? idCanillaOriginal)
{
using var connection = _connectionFactory.CreateConnection(); // Asumiendo _cf es tu DbConnectionFactory
var sqlBuilder = new StringBuilder(@"
SELECT
h.Id_Canilla, h.Legajo, h.NomApe, h.Parada, h.Id_Zona, h.Accionista, h.Obs,
h.Empresa, h.Baja, h.FechaBaja,
h.Id_Usuario, h.FechaMod, h.TipoMod,
u.Nombre + ' ' + u.Apellido AS NombreUsuarioModifico
FROM dbo.dist_dtCanillas_H h
JOIN dbo.gral_Usuarios u ON h.Id_Usuario = u.Id
WHERE 1=1");
var parameters = new DynamicParameters();
if (fechaDesde.HasValue) { sqlBuilder.Append(" AND h.FechaMod >= @FechaDesdeParam"); parameters.Add("FechaDesdeParam", fechaDesde.Value.Date); }
if (fechaHasta.HasValue) { sqlBuilder.Append(" AND h.FechaMod <= @FechaHastaParam"); parameters.Add("FechaHastaParam", fechaHasta.Value.Date.AddDays(1).AddTicks(-1)); }
if (idUsuarioModifico.HasValue) { sqlBuilder.Append(" AND h.Id_Usuario = @IdUsuarioModificoParam"); parameters.Add("IdUsuarioModificoParam", idUsuarioModifico.Value); }
if (!string.IsNullOrWhiteSpace(tipoModificacion)) { sqlBuilder.Append(" AND h.TipoMod = @TipoModParam"); parameters.Add("TipoModParam", tipoModificacion); }
if (idCanillaOriginal.HasValue) { sqlBuilder.Append(" AND h.Id_Canilla = @IdCanillaOriginalParam"); parameters.Add("IdCanillaOriginalParam", idCanillaOriginal.Value); }
sqlBuilder.Append(" ORDER BY h.FechaMod DESC;");
try
{
var result = await connection.QueryAsync<CanillaHistorico, string, (CanillaHistorico, string)>(
sqlBuilder.ToString(),
(hist, userName) => (hist, userName),
parameters,
splitOn: "NombreUsuarioModifico"
);
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error al obtener historial de Canillitas (Maestro).");
return Enumerable.Empty<(CanillaHistorico, string)>();
}
}
}
}

View File

@@ -36,7 +36,7 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
if (fechaDesde.HasValue) { sqlBuilder.Append(" AND Fecha >= @FechaDesdeParam"); parameters.Add("FechaDesdeParam", fechaDesde.Value.Date); }
if (fechaHasta.HasValue) { sqlBuilder.Append(" AND Fecha <= @FechaHastaParam"); parameters.Add("FechaHastaParam", fechaHasta.Value.Date); }
if (idEmpresa.HasValue) { sqlBuilder.Append(" AND Id_Empresa = @IdEmpresaParam"); parameters.Add("IdEmpresaParam", idEmpresa.Value); }
sqlBuilder.Append(" ORDER BY Fecha DESC, Id_Empresa;");
try
@@ -101,10 +101,18 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
var inserted = await transaction.Connection!.QuerySingleAsync<ControlDevoluciones>(sqlInsert, nuevoControl, transaction);
if (inserted == null || inserted.IdControl == 0) throw new DataException("Error al crear control de devoluciones o ID no generado.");
await transaction.Connection!.ExecuteAsync(sqlHistorico, new {
IdControlParam = inserted.IdControl, IdEmpresaParam = inserted.IdEmpresa, FechaParam = inserted.Fecha,
EntradaParam = inserted.Entrada, SobrantesParam = inserted.Sobrantes, DetalleParam = inserted.Detalle, SinCargoParam = inserted.SinCargo,
IdUsuarioParam = idUsuario, FechaModParam = DateTime.Now, TipoModParam = "Creado"
await transaction.Connection!.ExecuteAsync(sqlHistorico, new
{
IdControlParam = inserted.IdControl,
IdEmpresaParam = inserted.IdEmpresa,
FechaParam = inserted.Fecha,
EntradaParam = inserted.Entrada,
SobrantesParam = inserted.Sobrantes,
DetalleParam = inserted.Detalle,
SinCargoParam = inserted.SinCargo,
IdUsuarioParam = idUsuario,
FechaModParam = DateTime.Now,
TipoModParam = "Creado"
}, transaction);
return inserted;
}
@@ -121,15 +129,23 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
UPDATE dbo.dist_dtCtrlDevoluciones SET
Entrada = @Entrada, Sobrantes = @Sobrantes, Detalle = @Detalle, SinCargo = @SinCargo
WHERE Id_Control = @IdControl;";
const string sqlHistorico = @"
const string sqlHistorico = @"
INSERT INTO dbo.dist_dtCtrlDevoluciones_H
(Id_Control, Id_Empresa, Fecha, Entrada, Sobrantes, Detalle, SinCargo, Id_Usuario, FechaMod, TipoMod)
VALUES (@IdControlParam, @IdEmpresaParam, @FechaParam, @EntradaParam, @SobrantesParam, @DetalleParam, @SinCargoParam, @IdUsuarioParam, @FechaModParam, @TipoModParam);";
await transaction.Connection!.ExecuteAsync(sqlHistorico, new {
IdControlParam = actual.IdControl, IdEmpresaParam = actual.IdEmpresa, FechaParam = actual.Fecha, // Datos originales
EntradaParam = actual.Entrada, SobrantesParam = actual.Sobrantes, DetalleParam = actual.Detalle, SinCargoParam = actual.SinCargo, // Valores ANTERIORES
IdUsuarioParam = idUsuario, FechaModParam = DateTime.Now, TipoModParam = "Actualizado"
await transaction.Connection!.ExecuteAsync(sqlHistorico, new
{
IdControlParam = actual.IdControl,
IdEmpresaParam = actual.IdEmpresa,
FechaParam = actual.Fecha, // Datos originales
EntradaParam = actual.Entrada,
SobrantesParam = actual.Sobrantes,
DetalleParam = actual.Detalle,
SinCargoParam = actual.SinCargo, // Valores ANTERIORES
IdUsuarioParam = idUsuario,
FechaModParam = DateTime.Now,
TipoModParam = "Actualizado"
}, transaction);
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlUpdate, controlAActualizar, transaction);
@@ -149,14 +165,66 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
(Id_Control, Id_Empresa, Fecha, Entrada, Sobrantes, Detalle, SinCargo, Id_Usuario, FechaMod, TipoMod)
VALUES (@IdControlParam, @IdEmpresaParam, @FechaParam, @EntradaParam, @SobrantesParam, @DetalleParam, @SinCargoParam, @IdUsuarioParam, @FechaModParam, @TipoModParam);";
await transaction.Connection!.ExecuteAsync(sqlHistorico, new {
IdControlParam = actual.IdControl, IdEmpresaParam = actual.IdEmpresa, FechaParam = actual.Fecha,
EntradaParam = actual.Entrada, SobrantesParam = actual.Sobrantes, DetalleParam = actual.Detalle, SinCargoParam = actual.SinCargo,
IdUsuarioParam = idUsuario, FechaModParam = DateTime.Now, TipoModParam = "Eliminado"
await transaction.Connection!.ExecuteAsync(sqlHistorico, new
{
IdControlParam = actual.IdControl,
IdEmpresaParam = actual.IdEmpresa,
FechaParam = actual.Fecha,
EntradaParam = actual.Entrada,
SobrantesParam = actual.Sobrantes,
DetalleParam = actual.Detalle,
SinCargoParam = actual.SinCargo,
IdUsuarioParam = idUsuario,
FechaModParam = DateTime.Now,
TipoModParam = "Eliminado"
}, transaction);
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlDelete, new { IdControlParam = idControl }, transaction);
return rowsAffected == 1;
}
public async Task<IEnumerable<(ControlDevolucionesHistorico Historial, string NombreUsuarioModifico)>> GetHistorialAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idUsuarioModifico, string? tipoModificacion,
int? idControlOriginal, int? idEmpresaOriginal, DateTime? fechaOriginal)
{
using var connection = _cf.CreateConnection();
var sqlBuilder = new StringBuilder(@"
SELECT
h.Id_Control, h.Id_Empresa, h.Fecha, h.Entrada, h.Sobrantes, h.Detalle, h.SinCargo,
h.Id_Usuario, h.FechaMod, h.TipoMod,
u.Nombre + ' ' + u.Apellido AS NombreUsuarioModifico
FROM dbo.dist_dtCtrlDevoluciones_H h
JOIN dbo.gral_Usuarios u ON h.Id_Usuario = u.Id
WHERE 1=1");
var parameters = new DynamicParameters();
if (fechaDesde.HasValue) { sqlBuilder.Append(" AND h.FechaMod >= @FechaDesdeParam"); parameters.Add("FechaDesdeParam", fechaDesde.Value.Date); }
if (fechaHasta.HasValue) { sqlBuilder.Append(" AND h.FechaMod <= @FechaHastaParam"); parameters.Add("FechaHastaParam", fechaHasta.Value.Date.AddDays(1).AddTicks(-1)); }
if (idUsuarioModifico.HasValue) { sqlBuilder.Append(" AND h.Id_Usuario = @IdUsuarioModificoParam"); parameters.Add("IdUsuarioModificoParam", idUsuarioModifico.Value); }
if (!string.IsNullOrWhiteSpace(tipoModificacion)) { sqlBuilder.Append(" AND h.TipoMod = @TipoModParam"); parameters.Add("TipoModParam", tipoModificacion); }
if (idControlOriginal.HasValue) { sqlBuilder.Append(" AND h.Id_Control = @IdControlOriginalParam"); parameters.Add("IdControlOriginalParam", idControlOriginal.Value); }
if (idEmpresaOriginal.HasValue) { sqlBuilder.Append(" AND h.Id_Empresa = @IdEmpresaOriginalParam"); parameters.Add("IdEmpresaOriginalParam", idEmpresaOriginal.Value); }
if (fechaOriginal.HasValue) { sqlBuilder.Append(" AND h.Fecha = @FechaOriginalParam"); parameters.Add("FechaOriginalParam", fechaOriginal.Value.Date); }
sqlBuilder.Append(" ORDER BY h.FechaMod DESC;");
try
{
var result = await connection.QueryAsync<ControlDevolucionesHistorico, string, (ControlDevolucionesHistorico, string)>(
sqlBuilder.ToString(),
(hist, userName) => (hist, userName),
parameters,
splitOn: "NombreUsuarioModifico"
);
return result;
}
catch (Exception ex)
{
_log.LogError(ex, "Error al obtener historial de Control de Devoluciones.");
return Enumerable.Empty<(ControlDevolucionesHistorico, string)>();
}
}
}
}

View File

@@ -1,4 +1,5 @@
using Dapper;
using GestionIntegral.Api.Dtos.Distribucion;
using GestionIntegral.Api.Models.Distribucion;
using Microsoft.Extensions.Logging;
using System; // Añadido para Exception
@@ -62,6 +63,30 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
}
}
public async Task<IEnumerable<DistribuidorDropdownDto?>> GetAllDropdownAsync()
{
var sqlBuilder = new StringBuilder(@"
SELECT
Id_Distribuidor AS IdDistribuidor, Nombre
FROM dbo.dist_dtDistribuidores
WHERE 1=1");
var parameters = new DynamicParameters();
sqlBuilder.Append(" ORDER BY Nombre;");
try
{
using var connection = _connectionFactory.CreateConnection();
return await connection.QueryAsync<DistribuidorDropdownDto>(
sqlBuilder.ToString(),
parameters
);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error al obtener todos los Distribuidores.");
return Enumerable.Empty<DistribuidorDropdownDto>();
}
}
public async Task<(Distribuidor? Distribuidor, string? NombreZona)> GetByIdAsync(int id)
{
const string sql = @"
@@ -90,6 +115,25 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
}
}
public async Task<DistribuidorLookupDto?> ObtenerLookupPorIdAsync(int id)
{
const string sql = @"
SELECT
Id_Distribuidor AS IdDistribuidor, Nombre
FROM dbo.dist_dtDistribuidores
WHERE Id_Distribuidor = @IdParam";
try
{
using var connection = _connectionFactory.CreateConnection();
return await connection.QuerySingleOrDefaultAsync<DistribuidorLookupDto>(sql, new { IdParam = id });
}
catch (Exception ex)
{
_logger.LogError(ex, "Error al obtener Distribuidor por ID: {IdDistribuidor}", id);
return null;
}
}
public async Task<Distribuidor?> GetByIdSimpleAsync(int id)
{
const string sql = @"
@@ -105,7 +149,7 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
}
catch (Exception ex)
{
_logger.LogError(ex, "Error al obtener Distribuidor (simple) por ID: {IdDistribuidor}", id);
_logger.LogError(ex, "Error al obtener Distribuidor (simple) por ID: {IdDistribuidor}", id);
return null;
}
}
@@ -127,7 +171,7 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
}
catch (Exception ex)
{
_logger.LogError(ex, "Error en ExistsByNroDocAsync. NroDoc: {NroDoc}", nroDoc);
_logger.LogError(ex, "Error en ExistsByNroDocAsync. NroDoc: {NroDoc}", nroDoc);
return true;
}
}
@@ -183,7 +227,7 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
OUTPUT INSERTED.Id_Distribuidor AS IdDistribuidor, INSERTED.Nombre, INSERTED.Contacto, INSERTED.NroDoc, INSERTED.Id_Zona AS IdZona,
INSERTED.Calle, INSERTED.Numero, INSERTED.Piso, INSERTED.Depto, INSERTED.Telefono, INSERTED.Email, INSERTED.Localidad
VALUES (@Nombre, @Contacto, @NroDoc, @IdZona, @Calle, @Numero, @Piso, @Depto, @Telefono, @Email, @Localidad);";
var connection = transaction.Connection!;
var inserted = await connection.QuerySingleAsync<Distribuidor>(sqlInsert, nuevoDistribuidor, transaction);
if (inserted == null || inserted.IdDistribuidor == 0) throw new DataException("Error al crear distribuidor o al obtener el ID generado.");
@@ -195,9 +239,21 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
await connection.ExecuteAsync(sqlInsertHistorico, new
{
IdDistribuidorParam = inserted.IdDistribuidor, NombreParam = inserted.Nombre, ContactoParam = inserted.Contacto, NroDocParam = inserted.NroDoc, IdZonaParam = inserted.IdZona,
CalleParam = inserted.Calle, NumeroParam = inserted.Numero, PisoParam = inserted.Piso, DeptoParam = inserted.Depto, TelefonoParam = inserted.Telefono, EmailParam = inserted.Email, LocalidadParam = inserted.Localidad,
IdUsuarioParam = idUsuario, FechaModParam = DateTime.Now, TipoModParam = "Creado"
IdDistribuidorParam = inserted.IdDistribuidor,
NombreParam = inserted.Nombre,
ContactoParam = inserted.Contacto,
NroDocParam = inserted.NroDoc,
IdZonaParam = inserted.IdZona,
CalleParam = inserted.Calle,
NumeroParam = inserted.Numero,
PisoParam = inserted.Piso,
DeptoParam = inserted.Depto,
TelefonoParam = inserted.Telefono,
EmailParam = inserted.Email,
LocalidadParam = inserted.Localidad,
IdUsuarioParam = idUsuario,
FechaModParam = DateTime.Now,
TipoModParam = "Creado"
}, transaction);
return inserted;
}
@@ -224,9 +280,21 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
await connection.ExecuteAsync(sqlInsertHistorico, new
{
IdDistribuidorParam = actual.IdDistribuidor, NombreParam = actual.Nombre, ContactoParam = actual.Contacto, NroDocParam = actual.NroDoc, IdZonaParam = actual.IdZona,
CalleParam = actual.Calle, NumeroParam = actual.Numero, PisoParam = actual.Piso, DeptoParam = actual.Depto, TelefonoParam = actual.Telefono, EmailParam = actual.Email, LocalidadParam = actual.Localidad,
IdUsuarioParam = idUsuario, FechaModParam = DateTime.Now, TipoModParam = "Actualizado"
IdDistribuidorParam = actual.IdDistribuidor,
NombreParam = actual.Nombre,
ContactoParam = actual.Contacto,
NroDocParam = actual.NroDoc,
IdZonaParam = actual.IdZona,
CalleParam = actual.Calle,
NumeroParam = actual.Numero,
PisoParam = actual.Piso,
DeptoParam = actual.Depto,
TelefonoParam = actual.Telefono,
EmailParam = actual.Email,
LocalidadParam = actual.Localidad,
IdUsuarioParam = idUsuario,
FechaModParam = DateTime.Now,
TipoModParam = "Actualizado"
}, transaction);
var rowsAffected = await connection.ExecuteAsync(sqlUpdate, distribuidorAActualizar, transaction);
@@ -251,13 +319,68 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
await connection.ExecuteAsync(sqlInsertHistorico, new
{
IdDistribuidorParam = actual.IdDistribuidor, NombreParam = actual.Nombre, ContactoParam = actual.Contacto, NroDocParam = actual.NroDoc, IdZonaParam = actual.IdZona,
CalleParam = actual.Calle, NumeroParam = actual.Numero, PisoParam = actual.Piso, DeptoParam = actual.Depto, TelefonoParam = actual.Telefono, EmailParam = actual.Email, LocalidadParam = actual.Localidad,
IdUsuarioParam = idUsuario, FechaModParam = DateTime.Now, TipoModParam = "Eliminado"
IdDistribuidorParam = actual.IdDistribuidor,
NombreParam = actual.Nombre,
ContactoParam = actual.Contacto,
NroDocParam = actual.NroDoc,
IdZonaParam = actual.IdZona,
CalleParam = actual.Calle,
NumeroParam = actual.Numero,
PisoParam = actual.Piso,
DeptoParam = actual.Depto,
TelefonoParam = actual.Telefono,
EmailParam = actual.Email,
LocalidadParam = actual.Localidad,
IdUsuarioParam = idUsuario,
FechaModParam = DateTime.Now,
TipoModParam = "Eliminado"
}, transaction);
var rowsAffected = await connection.ExecuteAsync(sqlDelete, new { IdParam = id }, transaction);
return rowsAffected == 1;
}
public async Task<IEnumerable<(DistribuidorHistorico Historial, string NombreUsuarioModifico)>> GetHistorialAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idUsuarioModifico, string? tipoModificacion,
int? idDistribuidorOriginal)
{
using var connection = _connectionFactory.CreateConnection(); // Asumiendo _connectionFactory
var sqlBuilder = new StringBuilder(@"
SELECT
h.Id_Distribuidor, h.Nombre, h.Contacto, h.NroDoc, h.Id_Zona, h.Calle, h.Numero,
h.Piso, h.Depto, h.Telefono, h.Email, h.Localidad,
h.Id_Usuario, h.FechaMod, h.TipoMod,
u.Nombre + ' ' + u.Apellido AS NombreUsuarioModifico
FROM dbo.dist_dtDistribuidores_H h
JOIN dbo.gral_Usuarios u ON h.Id_Usuario = u.Id
WHERE 1=1");
var parameters = new DynamicParameters();
if (fechaDesde.HasValue) { sqlBuilder.Append(" AND h.FechaMod >= @FechaDesdeParam"); parameters.Add("FechaDesdeParam", fechaDesde.Value.Date); }
if (fechaHasta.HasValue) { sqlBuilder.Append(" AND h.FechaMod <= @FechaHastaParam"); parameters.Add("FechaHastaParam", fechaHasta.Value.Date.AddDays(1).AddTicks(-1)); }
if (idUsuarioModifico.HasValue) { sqlBuilder.Append(" AND h.Id_Usuario = @IdUsuarioModificoParam"); parameters.Add("IdUsuarioModificoParam", idUsuarioModifico.Value); }
if (!string.IsNullOrWhiteSpace(tipoModificacion)) { sqlBuilder.Append(" AND h.TipoMod = @TipoModParam"); parameters.Add("TipoModParam", tipoModificacion); }
if (idDistribuidorOriginal.HasValue) { sqlBuilder.Append(" AND h.Id_Distribuidor = @IdDistribuidorOriginalParam"); parameters.Add("IdDistribuidorOriginalParam", idDistribuidorOriginal.Value); }
sqlBuilder.Append(" ORDER BY h.FechaMod DESC;");
try
{
var result = await connection.QueryAsync<DistribuidorHistorico, string, (DistribuidorHistorico, string)>(
sqlBuilder.ToString(),
(hist, userName) => (hist, userName),
parameters,
splitOn: "NombreUsuarioModifico"
);
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error al obtener historial de Distribuidores (Maestro)."); // Asumiendo _logger
return Enumerable.Empty<(DistribuidorHistorico, string)>();
}
}
}
}

View File

@@ -1,5 +1,6 @@
using Dapper;
using GestionIntegral.Api.Data.Repositories;
using GestionIntegral.Api.Dtos.Empresas;
using GestionIntegral.Api.Models.Distribucion;
using System.Collections.Generic;
using System.Data;
@@ -52,6 +53,25 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
}
}
public async Task<IEnumerable<EmpresaDropdownDto>> GetAllDropdownAsync()
{
var sqlBuilder = new StringBuilder("SELECT Id_Empresa AS IdEmpresa, Nombre FROM dbo.dist_dtEmpresas WHERE 1=1");
var parameters = new DynamicParameters();
sqlBuilder.Append(" ORDER BY Nombre;");
try
{
using (var connection = _connectionFactory.CreateConnection())
{
return await connection.QueryAsync<EmpresaDropdownDto>(sqlBuilder.ToString(), parameters);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error al obtener todas las Empresas.");
return Enumerable.Empty<EmpresaDropdownDto>();
}
}
public async Task<Empresa?> GetByIdAsync(int id)
{
var sql = "SELECT Id_Empresa AS IdEmpresa, Nombre, Detalle FROM dbo.dist_dtEmpresas WHERE Id_Empresa = @Id";
@@ -69,6 +89,23 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
}
}
public async Task<Empresa?> ObtenerLookupPorIdAsync(int id)
{
var sql = "SELECT Id_Empresa AS IdEmpresa, Nombre FROM dbo.dist_dtEmpresas WHERE Id_Empresa = @Id";
try
{
using (var connection = _connectionFactory.CreateConnection())
{
return await connection.QuerySingleOrDefaultAsync<Empresa>(sql, new { Id = id });
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error al obtener Empresa por ID: {IdEmpresa}", id);
return null;
}
}
public async Task<bool> ExistsByNameAsync(string nombre, int? excludeId = null)
{
var sqlBuilder = new StringBuilder("SELECT COUNT(1) FROM dbo.dist_dtEmpresas WHERE Nombre = @Nombre");
@@ -144,7 +181,8 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
}
// Insertar en historial
await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new {
await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new
{
IdEmpresa = insertedEmpresa.IdEmpresa,
insertedEmpresa.Nombre,
insertedEmpresa.Detalle,
@@ -172,7 +210,8 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
VALUES (@IdEmpresa, @NombreActual, @DetalleActual, @IdUsuario, @FechaMod, @TipoMod);";
// Insertar en historial (estado anterior)
await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new {
await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new
{
IdEmpresa = empresaActual.IdEmpresa,
NombreActual = empresaActual.Nombre,
DetalleActual = empresaActual.Detalle,
@@ -182,7 +221,8 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
}, transaction: transaction);
// Actualizar principal
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlUpdate, new {
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlUpdate, new
{
empresaAActualizar.Nombre,
empresaAActualizar.Detalle,
empresaAActualizar.IdEmpresa
@@ -202,7 +242,8 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
VALUES (@IdEmpresa, @Nombre, @Detalle, @IdUsuario, @FechaMod, @TipoMod);";
// Insertar en historial (estado antes de borrar)
await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new {
await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new
{
IdEmpresa = empresaActual.IdEmpresa,
empresaActual.Nombre,
empresaActual.Detalle,
@@ -216,5 +257,47 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
return rowsAffected == 1;
}
public async Task<IEnumerable<(EmpresaHistorico Historial, string NombreUsuarioModifico)>> GetHistorialAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idUsuarioModifico, string? tipoModificacion,
int? idEmpresaOriginal)
{
using var connection = _connectionFactory.CreateConnection(); // Asumiendo _cf
var sqlBuilder = new StringBuilder(@"
SELECT
h.Id_Empresa, h.Nombre, h.Detalle,
h.Id_Usuario, h.FechaMod, h.TipoMod,
u.Nombre + ' ' + u.Apellido AS NombreUsuarioModifico
FROM dbo.dist_dtEmpresas_H h
JOIN dbo.gral_Usuarios u ON h.Id_Usuario = u.Id
WHERE 1=1");
var parameters = new DynamicParameters();
if (fechaDesde.HasValue) { sqlBuilder.Append(" AND h.FechaMod >= @FechaDesdeParam"); parameters.Add("FechaDesdeParam", fechaDesde.Value.Date); }
if (fechaHasta.HasValue) { sqlBuilder.Append(" AND h.FechaMod <= @FechaHastaParam"); parameters.Add("FechaHastaParam", fechaHasta.Value.Date.AddDays(1).AddTicks(-1)); }
if (idUsuarioModifico.HasValue) { sqlBuilder.Append(" AND h.Id_Usuario = @IdUsuarioModificoParam"); parameters.Add("IdUsuarioModificoParam", idUsuarioModifico.Value); }
if (!string.IsNullOrWhiteSpace(tipoModificacion)) { sqlBuilder.Append(" AND h.TipoMod = @TipoModParam"); parameters.Add("TipoModParam", tipoModificacion); }
if (idEmpresaOriginal.HasValue) { sqlBuilder.Append(" AND h.Id_Empresa = @IdEmpresaOriginalParam"); parameters.Add("IdEmpresaOriginalParam", idEmpresaOriginal.Value); }
sqlBuilder.Append(" ORDER BY h.FechaMod DESC;");
try
{
var result = await connection.QueryAsync<EmpresaHistorico, string, (EmpresaHistorico, string)>(
sqlBuilder.ToString(),
(hist, userName) => (hist, userName),
parameters,
splitOn: "NombreUsuarioModifico"
);
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error al obtener historial de Empresas (Maestro)."); // Asumiendo _logger
return Enumerable.Empty<(EmpresaHistorico, string)>();
}
}
}
}

View File

@@ -292,5 +292,48 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlDelete, new { IdParteParam = idParte }, transaction);
return rowsAffected == 1;
}
public async Task<IEnumerable<(EntradaSalidaCanillaHistorico Historial, string NombreUsuarioModifico)>> GetHistorialAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idUsuarioModifico, string? tipoModificacion,
int? idParteOriginal)
{
using var connection = _cf.CreateConnection();
var sqlBuilder = new StringBuilder(@"
SELECT
h.Id_Parte, h.Id_Publicacion, h.Id_Canilla, h.Fecha,
h.CantSalida, h.CantEntrada, h.Id_Precio, h.Id_Recargo, h.Id_PorcMon, h.Observacion,
h.Id_Usuario, h.FechaMod, h.TipoMod,
u.Nombre + ' ' + u.Apellido AS NombreUsuarioModifico
FROM dbo.dist_EntradasSalidasCanillas_H h
JOIN dbo.gral_Usuarios u ON h.Id_Usuario = u.Id
WHERE 1=1");
var parameters = new DynamicParameters();
if (fechaDesde.HasValue) { sqlBuilder.Append(" AND h.FechaMod >= @FechaDesdeParam"); parameters.Add("FechaDesdeParam", fechaDesde.Value.Date); }
if (fechaHasta.HasValue) { sqlBuilder.Append(" AND h.FechaMod <= @FechaHastaParam"); parameters.Add("FechaHastaParam", fechaHasta.Value.Date.AddDays(1).AddTicks(-1)); }
if (idUsuarioModifico.HasValue) { sqlBuilder.Append(" AND h.Id_Usuario = @IdUsuarioModificoParam"); parameters.Add("IdUsuarioModificoParam", idUsuarioModifico.Value); }
if (!string.IsNullOrWhiteSpace(tipoModificacion)) { sqlBuilder.Append(" AND h.TipoMod = @TipoModParam"); parameters.Add("TipoModParam", tipoModificacion); }
if (idParteOriginal.HasValue) { sqlBuilder.Append(" AND h.Id_Parte = @IdParteOriginalParam"); parameters.Add("IdParteOriginalParam", idParteOriginal.Value); }
sqlBuilder.Append(" ORDER BY h.FechaMod DESC;");
try
{
var result = await connection.QueryAsync<EntradaSalidaCanillaHistorico, string, (EntradaSalidaCanillaHistorico, string)>(
sqlBuilder.ToString(),
(hist, userName) => (hist, userName),
parameters,
splitOn: "NombreUsuarioModifico"
);
return result;
}
catch (Exception ex)
{
_log.LogError(ex, "Error al obtener historial de Entradas/Salidas Canillitas.");
return Enumerable.Empty<(EntradaSalidaCanillaHistorico, string)>();
}
}
}
}

View File

@@ -71,7 +71,7 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
return null;
}
}
public async Task<bool> ExistsByRemitoAndTipoForPublicacionAsync(int remito, string tipoMovimiento, int idPublicacion, int? excludeIdParte = null)
{
var sqlBuilder = new StringBuilder("SELECT COUNT(1) FROM dbo.dist_EntradasSalidas WHERE Remito = @RemitoParam AND TipoMovimiento = @TipoMovimientoParam AND Id_Publicacion = @IdPublicacionParam");
@@ -114,11 +114,22 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
var inserted = await transaction.Connection!.QuerySingleAsync<EntradaSalidaDist>(sqlInsert, nuevaES, transaction);
if (inserted == null || inserted.IdParte == 0) throw new DataException("Error al crear Entrada/Salida o ID no generado.");
await transaction.Connection!.ExecuteAsync(sqlHistorico, new {
IdParteHist = inserted.IdParte, IdPublicacionHist = inserted.IdPublicacion, IdDistribuidorHist = inserted.IdDistribuidor,
FechaHist = inserted.Fecha, TipoMovimientoHist = inserted.TipoMovimiento, CantidadHist = inserted.Cantidad, RemitoHist = inserted.Remito, ObservacionHist = inserted.Observacion,
IdPrecioHist = inserted.IdPrecio, IdRecargoHist = inserted.IdRecargo, IdPorcentajeHist = inserted.IdPorcentaje,
IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Creada"
await transaction.Connection!.ExecuteAsync(sqlHistorico, new
{
IdParteHist = inserted.IdParte,
IdPublicacionHist = inserted.IdPublicacion,
IdDistribuidorHist = inserted.IdDistribuidor,
FechaHist = inserted.Fecha,
TipoMovimientoHist = inserted.TipoMovimiento,
CantidadHist = inserted.Cantidad,
RemitoHist = inserted.Remito,
ObservacionHist = inserted.Observacion,
IdPrecioHist = inserted.IdPrecio,
IdRecargoHist = inserted.IdRecargo,
IdPorcentajeHist = inserted.IdPorcentaje,
IdUsuarioHist = idUsuario,
FechaModHist = DateTime.Now,
TipoModHist = "Creada"
}, transaction);
return inserted;
}
@@ -136,16 +147,27 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
Cantidad = @Cantidad, Observacion = @Observacion
-- Publicacion, Distribuidor, Fecha, TipoMovimiento, Remito, Ids de precio/recargo/porc no se modifican aquí
WHERE Id_Parte = @IdParte;";
const string sqlHistorico = @"
const string sqlHistorico = @"
INSERT INTO dbo.dist_EntradasSalidas_H
(Id_Parte, Id_Publicacion, Id_Distribuidor, Fecha, TipoMovimiento, Cantidad, Remito, Observacion, Id_Precio, Id_Recargo, Id_Porcentaje, Id_Usuario, FechaMod, TipoMod)
VALUES (@IdParteHist, @IdPublicacionHist, @IdDistribuidorHist, @FechaHist, @TipoMovimientoHist, @CantidadHist, @RemitoHist, @ObservacionHist, @IdPrecioHist, @IdRecargoHist, @IdPorcentajeHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
await transaction.Connection!.ExecuteAsync(sqlHistorico, new {
IdParteHist = actual.IdParte, IdPublicacionHist = actual.IdPublicacion, IdDistribuidorHist = actual.IdDistribuidor,
FechaHist = actual.Fecha, TipoMovimientoHist = actual.TipoMovimiento, CantidadHist = actual.Cantidad, RemitoHist = actual.Remito, ObservacionHist = actual.Observacion,
IdPrecioHist = actual.IdPrecio, IdRecargoHist = actual.IdRecargo, IdPorcentajeHist = actual.IdPorcentaje, // Valores ANTERIORES
IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Actualizada"
await transaction.Connection!.ExecuteAsync(sqlHistorico, new
{
IdParteHist = actual.IdParte,
IdPublicacionHist = actual.IdPublicacion,
IdDistribuidorHist = actual.IdDistribuidor,
FechaHist = actual.Fecha,
TipoMovimientoHist = actual.TipoMovimiento,
CantidadHist = actual.Cantidad,
RemitoHist = actual.Remito,
ObservacionHist = actual.Observacion,
IdPrecioHist = actual.IdPrecio,
IdRecargoHist = actual.IdRecargo,
IdPorcentajeHist = actual.IdPorcentaje, // Valores ANTERIORES
IdUsuarioHist = idUsuario,
FechaModHist = DateTime.Now,
TipoModHist = "Actualizada"
}, transaction);
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlUpdate, esAActualizar, transaction);
@@ -166,15 +188,69 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
(Id_Parte, Id_Publicacion, Id_Distribuidor, Fecha, TipoMovimiento, Cantidad, Remito, Observacion, Id_Precio, Id_Recargo, Id_Porcentaje, Id_Usuario, FechaMod, TipoMod)
VALUES (@IdParteHist, @IdPublicacionHist, @IdDistribuidorHist, @FechaHist, @TipoMovimientoHist, @CantidadHist, @RemitoHist, @ObservacionHist, @IdPrecioHist, @IdRecargoHist, @IdPorcentajeHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
await transaction.Connection!.ExecuteAsync(sqlHistorico, new {
IdParteHist = actual.IdParte, IdPublicacionHist = actual.IdPublicacion, IdDistribuidorHist = actual.IdDistribuidor,
FechaHist = actual.Fecha, TipoMovimientoHist = actual.TipoMovimiento, CantidadHist = actual.Cantidad, RemitoHist = actual.Remito, ObservacionHist = actual.Observacion,
IdPrecioHist = actual.IdPrecio, IdRecargoHist = actual.IdRecargo, IdPorcentajeHist = actual.IdPorcentaje,
IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Eliminada"
await transaction.Connection!.ExecuteAsync(sqlHistorico, new
{
IdParteHist = actual.IdParte,
IdPublicacionHist = actual.IdPublicacion,
IdDistribuidorHist = actual.IdDistribuidor,
FechaHist = actual.Fecha,
TipoMovimientoHist = actual.TipoMovimiento,
CantidadHist = actual.Cantidad,
RemitoHist = actual.Remito,
ObservacionHist = actual.Observacion,
IdPrecioHist = actual.IdPrecio,
IdRecargoHist = actual.IdRecargo,
IdPorcentajeHist = actual.IdPorcentaje,
IdUsuarioHist = idUsuario,
FechaModHist = DateTime.Now,
TipoModHist = "Eliminada"
}, transaction);
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlDelete, new { IdParteParam = idParte }, transaction);
return rowsAffected == 1;
}
public async Task<IEnumerable<(EntradaSalidaDistHistorico Historial, string NombreUsuarioModifico)>> GetHistorialAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idUsuarioModifico, string? tipoModificacion,
int? idParteOriginal)
{
using var connection = _cf.CreateConnection();
var sqlBuilder = new StringBuilder(@"
SELECT
h.Id_Parte, h.Id_Publicacion, h.Id_Distribuidor, h.Fecha, h.TipoMovimiento,
h.Cantidad, h.Remito, h.Observacion, h.Id_Precio, h.Id_Recargo, h.Id_Porcentaje,
h.Id_Usuario, h.FechaMod, h.TipoMod,
u.Nombre + ' ' + u.Apellido AS NombreUsuarioModifico
FROM dbo.dist_EntradasSalidas_H h
JOIN dbo.gral_Usuarios u ON h.Id_Usuario = u.Id
WHERE 1=1");
var parameters = new DynamicParameters();
if (fechaDesde.HasValue) { sqlBuilder.Append(" AND h.FechaMod >= @FechaDesdeParam"); parameters.Add("FechaDesdeParam", fechaDesde.Value.Date); }
if (fechaHasta.HasValue) { sqlBuilder.Append(" AND h.FechaMod <= @FechaHastaParam"); parameters.Add("FechaHastaParam", fechaHasta.Value.Date.AddDays(1).AddTicks(-1)); }
if (idUsuarioModifico.HasValue) { sqlBuilder.Append(" AND h.Id_Usuario = @IdUsuarioModificoParam"); parameters.Add("IdUsuarioModificoParam", idUsuarioModifico.Value); }
if (!string.IsNullOrWhiteSpace(tipoModificacion)) { sqlBuilder.Append(" AND h.TipoMod = @TipoModParam"); parameters.Add("TipoModParam", tipoModificacion); }
if (idParteOriginal.HasValue) { sqlBuilder.Append(" AND h.Id_Parte = @IdParteOriginalParam"); parameters.Add("IdParteOriginalParam", idParteOriginal.Value); }
sqlBuilder.Append(" ORDER BY h.FechaMod DESC;");
try
{
var result = await connection.QueryAsync<EntradaSalidaDistHistorico, string, (EntradaSalidaDistHistorico, string)>(
sqlBuilder.ToString(),
(hist, userName) => (hist, userName),
parameters,
splitOn: "NombreUsuarioModifico"
);
return result;
}
catch (Exception ex)
{
_log.LogError(ex, "Error al obtener historial de Entradas/Salidas Distribuidores.");
return Enumerable.Empty<(EntradaSalidaDistHistorico, string)>();
}
}
}
}

View File

@@ -0,0 +1,22 @@
using GestionIntegral.Api.Models.Distribucion;
using System;
using System.Collections.Generic;
using System.Data;
using System.Threading.Tasks;
namespace GestionIntegral.Api.Data.Repositories.Distribucion
{
public interface ICambioParadaRepository
{
Task<IEnumerable<CambioParadaCanilla>> GetByCanillaAsync(int idCanilla);
Task<CambioParadaCanilla?> GetByIdAsync(int idRegistro);
Task<CambioParadaCanilla?> GetCurrentParadaAsync(int idCanilla, IDbTransaction? transaction = null);
Task<CambioParadaCanilla?> CreateAsync(CambioParadaCanilla nuevaParada, int idUsuario, IDbTransaction transaction);
Task<bool> UpdateVigenciaHAsync(int idRegistro, DateTime vigenciaH, int idUsuario, IDbTransaction transaction);
Task<bool> DeleteAsync(int idRegistro, int idUsuario, IDbTransaction transaction);
Task<IEnumerable<(CambioParadaCanillaHistorial Historial, string NombreUsuarioModifico, string NombreCanilla)>> GetCambiosParadaHistorialAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idUsuarioModifico, string? tipoModificacion,
int? idCanillaOriginal);
}
}

View File

@@ -2,18 +2,23 @@ using GestionIntegral.Api.Models.Distribucion;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Data;
using GestionIntegral.Api.Dtos.Distribucion;
namespace GestionIntegral.Api.Data.Repositories.Distribucion
{
public interface ICanillaRepository
{
Task<IEnumerable<(Canilla Canilla, string NombreZona, string NombreEmpresa)>> GetAllAsync(string? nomApeFilter, int? legajoFilter, bool? soloActivos);
Task<IEnumerable<(Canilla Canilla, string? NombreZona, string? NombreEmpresa)>> GetAllAsync(string? nomApeFilter, int? legajoFilter, bool? soloActivos, bool? esAccionista);
Task<IEnumerable<CanillaDropdownDto>> GetAllDropdownAsync(bool? esAccionista, bool? soloActivos);
Task<(Canilla? Canilla, string? NombreZona, string? NombreEmpresa)> GetByIdAsync(int id);
Task<Canilla?> GetByIdSimpleAsync(int id); // Para obtener solo la entidad Canilla
Task<Canilla?> CreateAsync(Canilla nuevoCanilla, int idUsuario, IDbTransaction transaction);
Task<bool> UpdateAsync(Canilla canillaAActualizar, int idUsuario, IDbTransaction transaction);
Task<bool> ToggleBajaAsync(int id, bool darDeBaja, DateTime? fechaBaja, int idUsuario, IDbTransaction transaction);
Task<bool> ExistsByLegajoAsync(int legajo, int? excludeIdCanilla = null);
// IsInUse no es tan directo, ya que las liquidaciones se marcan. Se podría verificar dist_EntradasSalidasCanillas no liquidadas.
Task<IEnumerable<(CanillaHistorico Historial, string NombreUsuarioModifico)>> GetHistorialAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idUsuarioModifico, string? tipoModificacion,
int? idCanillaOriginal); // Para filtrar por un canillita específico
}
}

Some files were not shown because too many files have changed in this diff Show More