Se modifico seccion de reportes

This commit is contained in:
2025-09-12 15:59:13 -06:00
parent 6c020d4799
commit 2e35375a3a
3 changed files with 1026 additions and 209 deletions

View File

@@ -0,0 +1,440 @@
{
"models": [
{
"model": "Registro501",
"name": "Datos generales",
"module": "datastage",
"type": "excel",
"fields": [
"id", "patente", "pedimento", "seccion_aduanera", "tipo_operacion", "clave_documento",
"seccion_aduanera_entrada", "curp_contribuyente", "rfc", "curp_agente_a", "tipo_cambio",
"total_fletes", "total_seguros", "total_embalajes", "total_incrementables", "total_deducibles",
"peso_bruto_mercancia", "medio_transporte_salida", "medio_transporte_arribo",
"medio_transporte_entrada_salida", "destino_mercancia", "nombre_contribuyente",
"calle_contribuyente", "num_interior_contribuyente", "num_exterior_contribuyente",
"cp_contribuyente", "municipio_contribuyente", "entidad_fed_contribuyente", "pais_contribuyente",
"tipo_pedimento", "fecha_recepcion_pedimento", "fecha_pago_real", "organizacion", "consulta",
"datastage"
],
"filters": {
"patente": "",
"pedimento": "",
"seccion_aduanera": "",
"tipo_operacion": "",
"organizacion": "",
"datastage": ""
}
},
{
"model": "Registro502",
"name": "Transporte de las mercancías",
"module": "datastage",
"type": "excel",
"fields": [
"id", "pedimento", "seccion_aduanera", "rfc_transportista", "curp_transportista",
"nombre_transportista", "pais_transporte", "identificador_transporte", "fecha_pago_real",
"organizacion", "created_by", "consulta", "datastage", "patente"
],
"filters": {
"patente": "",
"pedimento": "",
"fecha_pago_real": "",
"organizacion": "",
"datastage": ""
}
},
{
"model": "Registro503",
"name": "Guías",
"module": "datastage",
"type": "excel",
"fields": [
"id", "patente", "pedimento", "seccion_aduanera", "numero_guia", "tipo_guia",
"fecha_pago_real", "organizacion", "created_by", "consulta", "datastage"
],
"filters": {
"patente": "",
"pedimento": "",
"fecha_pago_real": "",
"organizacion": "",
"datastage": ""
}
},
{
"model": "Registro504",
"name": "Contenedores",
"module": "datastage",
"type": "excel",
"fields": [
"id", "patente", "pedimento", "seccion_aduanera", "num_contenedor", "tipo_contenedor",
"fecha_pago_real", "organizacion", "created_by", "consulta", "datastage"
],
"filters": {
"patente": "",
"pedimento": "",
"num_contenedor": "",
"organizacion": "",
"datastage": ""
}
},
{
"model": "Registro505",
"name": "Facturas",
"module": "datastage",
"type": "excel",
"fields": [
"id", "pedimento", "seccion_aduanera", "fecha_facturacion", "numero_factura",
"termino_facturacion", "moneda_facturacion", "valor_dolares", "valor_moneda_extranjera",
"pais_facturacion", "entidad_fed_facturacion", "indent_fiscal_proveedor",
"proveedor_mercancia", "calle_proveedor", "num_interior_proveedor", "num_exterior_proveedor",
"cp_proveedor", "municipio_proveedor", "fecha_pago_real", "organizacion", "created_by",
"consulta", "datastage", "patente"
],
"filters": {
"pedimento": "",
"numero_factura": "",
"organizacion": "",
"datastage": ""
}
},
{
"model": "Registro506",
"name": "Fechas del pedimento",
"module": "datastage",
"type": "excel",
"fields": [
"id", "patente", "pedimento", "seccion_aduanera", "tipo_fecha", "fecha_operacion",
"fecha_validacion_pago_r", "organizacion", "created_by", "consulta", "datastage"
],
"filters": {
"patente": "",
"fecha_operacion": "",
"organizacion": "",
"datastage": ""
}
},
{
"model": "Registro507",
"name": "Casos del pedimento",
"module": "datastage",
"type": "excel",
"fields": [
"id", "patente", "pedimento", "seccion_aduanera", "clave_caso", "identificador_caso",
"tipo_pedimento", "complemento_caso", "fecha_validacion_pago_r", "organizacion",
"created_by", "consulta", "datastage"
],
"filters": {
"patente": "",
"clave_caso": "",
"organizacion": "",
"datastage": ""
}
},
{
"model": "Registro508",
"name": "Cuentas aduaneras de garantía del pedimento",
"module": "datastage",
"type": "excel",
"fields": [
"id", "patente", "pedimento", "seccion_aduanera", "institucion_emisora", "numero_cuenta",
"folio_constancia", "fecha_constancia", "tipo_cuenta", "clave_garantia",
"valor_unitario_titulo", "total_garantia", "cantidad_unidades", "titulos_asignados",
"fecha_pago_real", "organizacion", "created_by", "consulta", "datastage"
],
"filters": {
"patente": "",
"institucion_emisora": "",
"organizacion": "",
"datastage": ""
}
},
{
"model": "Registro509",
"name": "Tasas del pedimento",
"module": "datastage",
"type": "excel",
"fields": [
"id", "patente", "pedimento", "seccion_aduanera", "clave_contribucion",
"tasa_contribucion", "tipo_tasa", "tipo_pedimento", "fecha_pago_real",
"organizacion", "created_by", "consulta", "datastage"
],
"filters": {
"patente": "",
"clave_contribucion": "",
"organizacion": "",
"datastage": ""
}
},
{
"model": "Registro510",
"name": "Contribuciones del pedimento",
"module": "datastage",
"type": "excel",
"fields": [
"id", "patente", "pedimento", "seccion_aduanera", "clave_contribucion",
"tasa_contribucion", "tipo_tasa", "tipo_pedimento", "fecha_pago_real",
"organizacion", "created_by", "datastage", "consulta", "forma_pago", "importe_pago"
],
"filters": {
"patente": "",
"clave_contribucion": "",
"organizacion": "",
"datastage": ""
}
},
{
"model": "Registro511",
"name": "Observaciones del pedimento",
"module": "datastage",
"type": "excel",
"fields": [
"id", "patente", "pedimento", "seccion_aduanera", "secuencia_observacion",
"observaciones", "tipo_pedimento", "fecha_validacion_pago_r", "organizacion",
"created_by", "consulta", "datastage"
],
"filters": {
"patente": "",
"secuencia_observacion": "",
"organizacion": "",
"datastage": ""
}
},
{
"model": "Registro512",
"name": "Descargos de mercancías",
"module": "datastage",
"type": "excel",
"fields": [
"id", "patente", "pedimento", "seccion_aduanera", "patente_aduanal_orig",
"pedimento_original", "seccion_aduanera_desp_orig", "documento_original",
"fecha_operacion_orig", "fraccion_original", "unidad_medida", "mercancia_descargada",
"tipo_pedimento", "fecha_pago_real", "organizacion", "created_by", "consulta", "datastage"
],
"filters": {
"patente": "",
"patente_aduanal_orig": "",
"organizacion": "",
"datastage": ""
}
},
{
"model": "Registro520",
"name": "Destinatarios de la mercancía",
"module": "datastage",
"type": "excel",
"fields": [
"id", "patente", "pedimento", "seccion_aduanera", "indent_fiscal_destinatario",
"nombre_destinatario_mercancia", "calle_destinatario", "num_interior_destinatario",
"num_exterior_destinatario", "cp_destinatario", "municpio_destinatario",
"pais_destinatario", "fecha_pago_real", "organizacion", "created_at",
"consulta", "datastage"
],
"filters": {
"patente": "",
"indent_fiscal_destinatario": "",
"organizacion": "",
"datastage": ""
}
},
{
"model": "Registro551",
"name": "Partidas",
"module": "datastage",
"type": "excel",
"fields": [
"id", "patente", "pedimento", "seccion_aduanera", "fraccion", "secuencia_fraccion",
"subdivision_fraccion", "descripcion_mercancia", "precio_unitario", "valor_aduana",
"valor_comercial", "valor_dolares", "cantidad_um_comercial", "unidad_medida_comercial",
"cantidad_um_tarifa", "unidad_medida_tarifa", "valor_agregado", "clave_vinculacion",
"metodo_valorizacion", "codigo_mercancia_producto", "marca_mercancia_producto",
"modelo_mercancia_producto", "pais_origen_destino", "pais_comprador_vendedor",
"entidad_fed_origen", "entidad_fed_comprador", "entidad_fed_vendedor",
"tipo_operacion", "clave_documento", "fecha_pago_real", "organizacion",
"created_by", "consulta", "datastage", "entidad_fed_destino"
],
"filters": {
"patente": "",
"fraccion": "",
"organizacion": "",
"datastage": ""
}
},
{
"model": "Registro552",
"name": "Mercancías",
"module": "datastage",
"type": "excel",
"fields": [
"id", "patente", "pedimento", "seccion_aduanera", "fraccion", "secuencia_fraccion",
"vin_numero_serie", "kilometraje_vehiculo", "fecha_pago_real", "organizacion",
"created_by", "consulta", "datastage"
],
"filters": {
"patente": "",
"fraccion": "",
"organizacion": "",
"datastage": ""
}
},
{
"model": "Registro553",
"name": "Permiso de la partida",
"module": "datastage",
"type": "excel",
"fields": [
"id", "patente", "pedimento", "seccion_aduanera", "fraccion", "secuencia_fraccion",
"clave_permiso", "firma_descargo", "numero_permiso", "valor_comercial_dolares",
"cantidad_mum_tarifa", "fecha_pago_real", "organizacion", "created_by",
"datastage", "consulta"
],
"filters": {
"patente": "",
"fraccion": "",
"organizacion": "",
"datastage": ""
}
},
{
"model": "Registro554",
"name": "Casos de la partida",
"module": "datastage",
"type": "excel",
"fields": [
"id", "patente", "pedimento", "seccion_aduanera", "fraccion", "secuencia_fraccion",
"clave_caso", "identificador_caso", "complemento_caso", "fecha_pago_real",
"organizacion", "created_by", "consulta", "datastage"
],
"filters": {
"patente": "",
"fraccion": "",
"organizacion": "",
"datastage": ""
}
},
{
"model": "Registro555",
"name": "Cuentas aduaneras de garantía de la partida",
"module": "datastage",
"type": "excel",
"fields": [
"id", "patente", "pedimento", "seccion_aduanera", "fraccion", "secuencia_fraccion",
"institucion_emisora", "numero_cuenta", "folio_constancia", "fecha_constancia",
"clave_garantia", "valor_unitario_titulo", "total_garantia", "cantidad_unidades_medida",
"titulos_asignados", "fecha_pago_real", "organizacion", "datastage", "created_by",
"consulta"
],
"filters": {
"patente": "",
"fraccion": "",
"organizacion": "",
"datastage": ""
}
},
{
"model": "Registro556",
"name": "Tasas de las contribuciones de la partida",
"module": "datastage",
"type": "excel",
"fields": [
"id", "patente", "pedimento", "seccion_aduanera", "clave_contribucion",
"tasa_contribucion", "tipo_tasa", "fecha_pago_real", "organizacion",
"created_by", "consulta", "datastage", "fraccion", "secuencia_fraccion"
],
"filters": {
"patente": "",
"clave_contribucion": "",
"organizacion": "",
"datastage": ""
}
},
{
"model": "Registro557",
"name": "Contribuciones de la partida",
"module": "datastage",
"type": "excel",
"fields": [
"id", "patente", "pedimento", "seccion_aduanera", "fraccion", "secuencia_fraccion",
"clave_contribucion", "forma_pago", "importe_pago", "fecha_pago_real",
"organizacion", "created_by", "datastage", "consulta"
],
"filters": {
"patente": "",
"fraccion": "",
"organizacion": "",
"datastage": ""
}
},
{
"model": "Registro558",
"name": "Observaciones de la partida",
"module": "datastage",
"type": "excel",
"fields": [
"id", "patente", "pedimento", "seccion_aduanera", "fraccion", "secuencia_fraccion",
"secuencia_observacion", "observaciones", "fecha_pago_real", "organizacion",
"created_by", "datastage", "consulta"
],
"filters": {
"patente": "",
"fraccion": "",
"organizacion": "",
"datastage": ""
}
},
{
"model": "RegistroSel",
"name": "Registros de selección",
"module": "datastage",
"type": "excel",
"fields": [
"id", "patente", "pedimento", "seccion_aduanera", "consecutivo_remesa",
"numero_seleccion", "fecha_seleccion", "hora_seleccion", "semaforo_fiscal",
"clave_documento", "tipo_operacion", "organizacion", "created_by",
"consulta", "datastage"
],
"filters": {
"patente": "",
"consecutivo_remesa": "",
"organizacion": "",
"datastage": ""
}
},
{
"model": "Registro701",
"name": "Rectificaciones",
"module": "datastage",
"type": "excel",
"fields": [
"id", "patente", "pedimento", "seccion_aduanera", "clave_documento",
"fecha_pago", "pedimento_anterior", "patente_anterior", "seccion_aduanera_anterior",
"documento_anterior", "fecha_operacion_anterior", "pedimento_original",
"patente_aduanal_orig", "seccion_aduanera_desp_orig", "fecha_pago_real",
"organizacion", "created_by", "consulta", "datastage"
],
"filters": {
"patente": "",
"pedimento_anterior": "",
"organizacion": "",
"datastage": ""
}
},
{
"model": "Registro702",
"name": "Diferencias de contribuciones del pedimento",
"module": "datastage",
"type": "excel",
"fields": [
"id", "patente", "pedimento", "seccion_aduanera", "clave_contribucion",
"forma_pago", "importe_pago", "tipo_pedimento", "fecha_pago_real",
"organizacion", "created_by", "consulta", "datastage"
],
"filters": {
"patente": "",
"clave_contribucion": "",
"organizacion": "",
"datastage": ""
}
}
]
}

View File

@@ -0,0 +1,50 @@
{
"models": [
{
"model": "Pedimento",
"name": "Pedimentos cargados",
"module": "customs",
"type": "excel",
"fields": [
"id",
"pedimento",
"pedimento_app",
"patente",
"aduana",
"regimen",
"tipo_operacion",
"clave_pedimento",
"fecha_inicio",
"fecha_fin",
"fecha_pago",
"alerta",
"contribuyente",
"agente_aduanal",
"curp_apoderado",
"importe_total",
"saldo_disponible",
"importe_pedimento",
"existe_expediente",
"remesas",
"numero_partidas",
"numero_operacion",
"created_at",
"updated_at"
],
"filters": {
"pedimento": "",
"patente": "",
"aduana": "",
"fecha_inicio": "",
"fecha_fin": "",
"fecha_pago": "",
"tipo_operacion": "",
"clave_pedimento": "",
"contribuyente": "",
"alerta": "",
"existe_expediente": "",
"remesas": ""
}
}
]
}

View File

@@ -1,80 +1,102 @@
import React, { useState } from "react";
import React, { useState, useEffect } from 'react';
import { fetchWithAuth } from '../fetchWithAuth';
import datastageModelsData from '../data/datastageModels.json';
import pedimentosModelsData from '../data/pedimentosModels.json';
const API_URL = import.meta.env.VITE_EFC_API_URL;
// Animación fade-in/slide-up para bloques
const fadeInSlideUp = `@keyframes fadein-slideup {
0% { opacity: 0; transform: translateY(40px); }
100% { opacity: 1; transform: translateY(0); }
}`;
if (typeof document !== 'undefined' && !document.getElementById('fadein-slideup-reports')) {
// Animaciones
const animations = `
@keyframes fadein-slideup {
0% { opacity: 0; transform: translateY(40px); }
100% { opacity: 1; transform: translateY(0); }
}
@keyframes shimmer {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
@keyframes bounce-slow {
0%, 100% { transform: translateY(0) scale(1); }
50% { transform: translateY(-8px) scale(1.05); }
}
@keyframes pulse-soft {
0%, 100% { opacity: 1; }
50% { opacity: 0.8; }
}
`;
// Inyectar estilos de animación
if (typeof document !== 'undefined' && !document.getElementById('reports-animations')) {
const style = document.createElement('style');
style.id = 'fadein-slideup-reports';
style.innerHTML = fadeInSlideUp;
style.id = 'reports-animations';
style.innerHTML = animations;
document.head.appendChild(style);
}
export default function Reports() {
const [isExporting, setIsExporting] = useState(false);
const [exportFormat, setExportFormat] = useState('excel');
const [showExportSuccess, setShowExportSuccess] = useState(false);
// Estado para formato de exportación personalizado
const [showFormatSelector, setShowFormatSelector] = useState(false);
// Estado para pestañas
const [activeTab, setActiveTab] = useState('pedimentos');
// JSON de modelos para Datastage
const datastageModels = [
{ model: "Registro500", name: "Validación y reconocimiento", fields: ["id", "patente", "pedimento", "seccion_aduanera", "consecutivo_remesa", "numero_seleccion", "fecha_inicio_reconocimiento", "hora_inicio_reconocimiento", "fecha_fin_reconocimiento", "hora_fin_reconocimiento", "fraccion", "secuencia_fraccion", "clave_documento", "tipo_operacion", "grado_incidencia", "fecha_seleccion", "organizacion", "consulta", "datastage", "created_at", "updated_at"], filters: {patente: "", pedimento: "", seccion_aduanera: "", fecha_seleccion: "", organizacion: "", datastage: ""}},
{ model: "Registro501", name: "Datos generales", fields: ["id", "patente", "pedimento", "seccion_aduanera", "tipo_operacion", "clave_documento", "seccion_aduanera_entrada", "curp_contribuyente", "rfc", "curp_agente_a", "tipo_cambio", "total_fletes", "total_seguros", "total_embalajes", "total_incrementables", "total_deducibles", "peso_bruto_mercancia", "medio_transporte_salida", "medio_transporte_arribo", "medio_transporte_entrada_salida", "destino_mercancia", "nombre_contribuyente", "calle_contribuyente", "num_interior_contribuyente", "num_exterior_contribuyente", "cp_contribuyente", "municipio_contribuyente", "entidad_fed_contribuyente", "pais_contribuyente", "tipo_pedimento", "fecha_recepcion_pedimento", "fecha_pago_real", "organizacion", "consulta", "datastage"], filters: {patente: "", pedimento: "", seccion_aduanera: "", tipo_operacion: "", organizacion: "", datastage: ""}},
{ model: "Registro502", name: "Transporte de las mercancías", fields: ["id", "pedimento", "seccion_aduanera", "rfc_transportista", "curp_transportista", "nombre_transportista", "pais_transporte", "identificador_transporte", "fecha_pago_real", "organizacion", "created_by", "consulta", "datastage", "patente"], filters: {patente: "", pedimento: "", fecha_pago_real: "", organizacion: "", datastage: ""}},
{ model: "Registro503", name: "Guías", fields: ["id", "patente", "pedimento", "seccion_aduanera", "numero_guia", "tipo_guia", "fecha_pago_real", "organizacion", "created_by", "consulta", "datastage"], filters: {patente: "", pedimento: "", fecha_pago_real: "", organizacion: "", datastage: ""}},
{ model: "Registro504", name: "Contenedores", fields: ["id", "patente", "pedimento", "seccion_aduanera", "num_contenedor", "tipo_contenedor", "fecha_pago_real", "organizacion", "created_by", "consulta", "datastage"], filters: {patente: "", pedimento: "", num_contenedor: "", organizacion: "", datastage: ""}},
{ model: "Registro505", name: "Facturas", fields: ["id", "pedimento", "seccion_aduanera", "fecha_facturacion", "numero_factura", "termino_facturacion", "moneda_facturacion", "valor_dolares", "valor_moneda_extranjera", "pais_facturacion", "entidad_fed_facturacion", "indent_fiscal_proveedor", "proveedor_mercancia", "calle_proveedor", "num_interior_proveedor", "num_exterior_proveedor", "cp_proveedor", "municipio_proveedor", "fecha_pago_real", "organizacion", "created_by", "consulta", "datastage", "patente"], filters: {pedimento: "", numero_factura: "", organizacion: "", datastage: ""}},
{ model: "Registro506", name: "Fechas del pedimento", fields: ["id", "patente", "pedimento", "seccion_aduanera", "tipo_fecha", "fecha_operacion", "fecha_validacion_pago_r", "organizacion", "created_by", "consulta", "datastage"], filters: {patente: "", fecha_operacion: "", organizacion: "", datastage: ""}},
{ model: "Registro507", name: "Casos del pedimento", fields: ["id", "patente", "pedimento", "seccion_aduanera", "clave_caso", "identificador_caso", "tipo_pedimento", "complemento_caso", "fecha_validacion_pago_r", "organizacion", "created_by", "consulta", "datastage"], filters: {patente: "", clave_caso: "", organizacion: "", datastage: ""}},
{ model: "Registro508", name: "Cuentas aduaneras de garantía del pedimento", fields: ["id", "patente", "pedimento", "seccion_aduanera", "institucion_emisora", "numero_cuenta", "folio_constancia", "fecha_constancia", "tipo_cuenta", "clave_garantia", "valor_unitario_titulo", "total_garantia", "cantidad_unidades", "titulos_asignados", "fecha_pago_real", "organizacion", "created_by", "consulta", "datastage"], filters: {patente: "", institucion_emisora: "", organizacion: "", datastage: ""}},
{ model: "Registro509", name: "Tasas del pedimento", fields: ["id", "patente", "pedimento", "seccion_aduanera", "clave_contribucion", "tasa_contribucion", "tipo_tasa", "tipo_pedimento", "fecha_pago_real", "organizacion", "created_by", "consulta", "datastage"], filters: {patente: "", clave_contribucion: "", organizacion: "", datastage: ""}},
{ model: "Registro510", name: "Contribuciones del pedimento", fields: ["id", "patente", "pedimento", "seccion_aduanera", "clave_contribucion", "tasa_contribucion", "tipo_tasa", "tipo_pedimento", "fecha_pago_real", "organizacion", "created_by", "datastage", "consulta", "forma_pago", "importe_pago"], filters: {patente: "", clave_contribucion: "", organizacion: "", datastage: ""}},
{ model: "Registro511", name: "Observaciones del pedimento", fields: ["id", "patente", "pedimento", "seccion_aduanera", "secuencia_observacion", "observaciones", "tipo_pedimento", "fecha_validacion_pago_r", "organizacion", "created_by", "consulta", "datastage"], filters: {patente: "", secuencia_observacion: "", organizacion: "", datastage: ""}},
{ model: "Registro512", name: "Descargos de mercancías", fields: ["id", "patente", "pedimento", "seccion_aduanera", "patente_aduanal_orig", "pedimento_original", "seccion_aduanera_desp_orig", "documento_original", "fecha_operacion_orig", "fraccion_original", "unidad_medida", "mercancia_descargada", "tipo_pedimento", "fecha_pago_real", "organizacion", "created_by", "consulta", "datastage"], filters: {patente: "", patente_aduanal_orig: "", organizacion: "", datastage: ""}},
{ model: "Registro520", name: "Destinatarios de la mercancía", fields: ["id", "patente", "pedimento", "seccion_aduanera", "indent_fiscal_destinatario", "nombre_destinatario_mercancia", "calle_destinatario", "num_interior_destinatario", "num_exterior_destinatario", "cp_destinatario", "municpio_destinatario", "pais_destinatario", "fecha_pago_real", "organizacion", "created_at", "consulta", "datastage"], filters: {patente: "", indent_fiscal_destinatario: "", organizacion: "", datastage: ""}},
{ model: "Registro551", name: "Partidas", fields: ["id", "patente", "pedimento", "seccion_aduanera", "fraccion", "secuencia_fraccion", "subdivision_fraccion", "descripcion_mercancia", "precio_unitario", "valor_aduana", "valor_comercial", "valor_dolares", "cantidad_um_comercial", "unidad_medida_comercial", "cantidad_um_tarifa", "unidad_medida_tarifa", "valor_agregado", "clave_vinculacion", "metodo_valorizacion", "codigo_mercancia_producto", "marca_mercancia_producto", "modelo_mercancia_producto", "pais_origen_destino", "pais_comprador_vendedor", "entidad_fed_origen", "entidad_fed_comprador", "entidad_fed_vendedor", "tipo_operacion", "clave_documento", "fecha_pago_real", "organizacion", "created_by", "consulta", "datastage", "entidad_fed_destino"], filters: {patente: "", fraccion: "", organizacion: "", datastage: ""}},
{ model: "Registro552", name: "Mercancías", fields: ["id", "patente", "pedimento", "seccion_aduanera", "fraccion", "secuencia_fraccion", "vin_numero_serie", "kilometraje_vehiculo", "fecha_pago_real", "organizacion", "created_by", "consulta", "datastage"], filters: {patente: "", fraccion: "", organizacion: "", datastage: ""}},
{ model: "Registro553", name: "Permiso de la partida", fields: ["id", "patente", "pedimento", "seccion_aduanera", "fraccion", "secuencia_fraccion", "clave_permiso", "firma_descargo", "numero_permiso", "valor_comercial_dolares", "cantidad_mum_tarifa", "fecha_pago_real", "organizacion", "created_by", "datastage", "consulta"], filters: {patente: "", fraccion: "", organizacion: "", datastage: ""}},
{ model: "Registro554", name: "Casos de la partida", fields: ["id", "patente", "pedimento", "seccion_aduanera", "fraccion", "secuencia_fraccion", "clave_caso", "identificador_caso", "complemento_caso", "fecha_pago_real", "organizacion", "created_by", "consulta", "datastage"], filters: {patente: "", fraccion: "", organizacion: "", datastage: ""}},
{ model: "Registro555", name: "Cuentas aduaneras de garantía de la partida", fields: ["id", "patente", "pedimento", "seccion_aduanera", "fraccion", "secuencia_fraccion", "institucion_emisora", "numero_cuenta", "folio_constancia", "fecha_constancia", "clave_garantia", "valor_unitario_titulo", "total_garantia", "cantidad_unidades_medida", "titulos_asignados", "fecha_pago_real", "organizacion", "datastage", "created_by", "consulta"], filters: {patente: "", fraccion: "", organizacion: "", datastage: ""}},
{ model: "Registro556", name: "Tasas de las contribuciones de la partida", fields: ["id", "patente", "pedimento", "seccion_aduanera", "clave_contribucion", "tasa_contribucion", "tipo_tasa", "fecha_pago_real", "organizacion", "created_by", "consulta", "datastage", "fraccion", "secuencia_fraccion"], filters: {patente: "", clave_contribucion: "", organizacion: "", datastage: ""}},
{ model: "Registro557", name: "Contribuciones de la partida", fields: ["id", "patente", "pedimento", "seccion_aduanera", "fraccion", "secuencia_fraccion", "clave_contribucion", "forma_pago", "importe_pago", "fecha_pago_real", "organizacion", "created_by", "datastage", "consulta"], filters: {patente: "", fraccion: "", organizacion: "", datastage: ""}},
{ model: "Registro558", name: "Observaciones de la partida", fields: ["id", "patente", "pedimento", "seccion_aduanera", "fraccion", "secuencia_fraccion", "secuencia_observacion", "observaciones", "fecha_pago_real", "organizacion", "created_by", "datastage", "consulta"], filters: {patente: "", fraccion: "", organizacion: "", datastage: ""}},
{ model: "RegistroSel", fields: ["id", "patente", "pedimento", "seccion_aduanera", "consecutivo_remesa", "numero_seleccion", "fecha_seleccion", "hora_seleccion", "semaforo_fiscal", "clave_documento", "tipo_operacion", "organizacion", "created_by", "consulta", "datastage"], filters: {patente: "", consecutivo_remesa: "", organizacion: "", datastage: ""}},
{ model: "Registro701", name: "Rectificaciones", fields: ["id", "patente", "pedimento", "seccion_aduanera", "clave_documento", "fecha_pago", "pedimento_anterior", "patente_anterior", "seccion_aduanera_anterior", "documento_anterior", "fecha_operacion_anterior", "pedimento_original", "patente_aduanal_orig", "seccion_aduanera_desp_orig", "fecha_pago_real", "organizacion", "created_by", "consulta", "datastage"], filters: {patente: "", pedimento_anterior: "", organizacion: "", datastage: ""}},
{ model: "Registro702", name: "Diferencias de contribuciones del pedimento", fields: ["id", "patente", "pedimento", "seccion_aduanera", "clave_contribucion", "forma_pago", "importe_pago", "tipo_pedimento", "fecha_pago_real", "organizacion", "created_by", "consulta", "datastage"], filters: {patente: "", clave_contribucion: "", organizacion: "", datastage: ""}},
];
// Importar modelos
const datastageModels = datastageModelsData?.models || [];
const pedimentosModels = pedimentosModelsData?.models || [];
// Obtener el modelo inicial según la pestaña activa
const initialModels = activeTab === 'pedimentos' ? pedimentosModels : datastageModels;
const defaultModel = initialModels[0] || { model: '', fields: [], filters: {} };
// Estado para modelo seleccionado en Datastage
const [selectedModel, setSelectedModel] = useState(datastageModels[0].model);
// Estado para modelo seleccionado
const [selectedModel, setSelectedModel] = useState(defaultModel.model);
// Estado para campos seleccionados
const [selectedFields, setSelectedFields] = useState(datastageModels[0].fields);
const [selectedFields, setSelectedFields] = useState(defaultModel.fields);
// Estado para campo seleccionado en lista disponible
const [availableSelected, setAvailableSelected] = useState(null);
// Estado para los filtros
const [filters, setFilters] = useState(datastageModels[0].filters);
const [filters, setFilters] = useState(defaultModel.filters);
// Actualizar campos seleccionados al cambiar de modelo
// Actualizar campos seleccionados al cambiar de modelo o pestaña
React.useEffect(() => {
const modelObj = datastageModels.find(m => m.model === selectedModel);
setSelectedFields(modelObj.fields);
setAvailableSelected(null);
setFilters(modelObj.filters);
const models = activeTab === 'pedimentos' ? pedimentosModels : datastageModels;
// Al cambiar de pestaña, seleccionar el primer modelo de la pestaña actual
const newModel = models[0]?.model || '';
setSelectedModel(newModel);
const modelObj = models.find(m => m.model === newModel);
if (modelObj) {
setSelectedFields(modelObj.fields);
setAvailableSelected(null);
setFilters(modelObj.filters);
}
}, [activeTab]);
// Efecto separado para cambios de modelo dentro de la misma pestaña
React.useEffect(() => {
const models = activeTab === 'pedimentos' ? pedimentosModels : datastageModels;
const modelObj = models.find(m => m.model === selectedModel);
if (modelObj) {
setSelectedFields(modelObj.fields);
setAvailableSelected(null);
setFilters(modelObj.filters);
}
}, [selectedModel]);
// Encontrar el modelo actual
const currentModel = datastageModels.find(m => m.model === selectedModel);
// Obtener los modelos según la pestaña activa
const models = activeTab === 'pedimentos' ? pedimentosModels : datastageModels;
// Encontrar el modelo actual dentro de los modelos de la pestaña activa
const currentModel = models.find(m => m.model === selectedModel) || defaultModel;
// Campos disponibles (no seleccionados)
// Campos disponibles (no seleccionados) del modelo actual
const availableFields = currentModel.fields.filter(f => !selectedFields.includes(f));
// Mover campo de disponible a seleccionado
@@ -112,32 +134,63 @@ export default function Reports() {
// Función para manejar la exportación del modelo
const handleExportModel = async () => {
if (selectedFields.length === 0) {
// Mostrar mensaje de error con estilo
const errorDiv = document.createElement('div');
errorDiv.className = 'fixed top-4 right-4 bg-red-50 border-l-4 border-red-500 p-4 rounded shadow-lg';
errorDiv.innerHTML = `
<div class="flex items-center">
<div class="flex-shrink-0">
<svg class="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" />
</svg>
</div>
<div class="ml-3">
<p class="text-sm text-red-700">Por favor selecciona al menos un campo para exportar</p>
</div>
</div>
`;
document.body.appendChild(errorDiv);
setTimeout(() => {
errorDiv.style.opacity = '0';
errorDiv.style.transform = 'translateX(100%)';
setTimeout(() => errorDiv.remove(), 300);
}, 3000);
return;
}
setIsExporting(true);
try {
// Construir el objeto de filtros con los valores no vacíos
const nonEmptyFilters = {};
for (const [key, value] of Object.entries(filters)) {
if (value && value.trim() !== '') {
nonEmptyFilters[key] = value.trim();
}
}
// Mostrar indicador de progreso
const progressDiv = document.createElement('div');
progressDiv.className = 'fixed bottom-4 right-4 bg-white p-4 rounded-lg shadow-xl border border-blue-100';
progressDiv.innerHTML = `
<div class="flex items-center gap-3">
<div class="animate-spin rounded-full h-5 w-5 border-b-2 border-blue-500"></div>
<p class="text-sm text-gray-600">Preparando exportación...</p>
</div>
`;
document.body.appendChild(progressDiv);
// Construir el payload
// Construir filtros no vacíos
const nonEmptyFilters = Object.entries(filters)
.reduce((acc, [key, value]) => {
if (value?.trim()) {
acc[key] = value.trim();
}
return acc;
}, {});
// Construir payload
const payload = {
model: currentModel.model,
module: currentModel.module,
fields: selectedFields,
type: 'csv', // Usamos CSV como formato por defecto
type: exportFormat,
...(Object.keys(nonEmptyFilters).length > 0 && { filters: nonEmptyFilters }),
};
// Solo agregar filtros si hay alguno con valor
if (Object.keys(nonEmptyFilters).length > 0) {
payload.filters = nonEmptyFilters;
}
// Log para debug
console.log('Sending payload:', payload);
// Realizar la petición POST
// Realizar la petición
const response = await fetchWithAuth(`${API_URL}/reports/exportmodel/`, {
method: 'POST',
headers: {
@@ -147,45 +200,51 @@ export default function Reports() {
});
if (!response.ok) {
// Intentar obtener el mensaje de error del backend
const errorData = await response.json().catch(() => ({}));
console.error('Error response:', {
status: response.status,
statusText: response.statusText,
errorData
});
throw new Error(errorData.error || errorData.message || 'Error al exportar el modelo');
}
// Obtener el blob del archivo según el tipo
// Actualizar mensaje de progreso
progressDiv.innerHTML = `
<div class="flex items-center gap-3">
<div class="animate-spin rounded-full h-5 w-5 border-b-2 border-green-500"></div>
<p class="text-sm text-gray-600">Preparando archivo...</p>
</div>
`;
const blob = await response.blob();
const contentType = payload.type === 'excel'
const contentType = exportFormat === 'excel'
? 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
: 'text/csv';
// Crear URL del blob
const url = window.URL.createObjectURL(
new Blob([blob], { type: contentType })
);
// Crear elemento <a> temporal para la descarga
const url = window.URL.createObjectURL(new Blob([blob], { type: contentType }));
const link = document.createElement('a');
const extension = exportFormat === 'excel' ? 'xlsx' : 'csv';
const fileName = `${currentModel.model}_${new Date().toISOString().split('T')[0]}.${extension}`;
link.href = url;
// Obtener el nombre del archivo del header Content-Disposition o usar uno por defecto
const contentDisposition = response.headers.get('Content-Disposition');
const extension = payload.type === 'csv' ? 'csv' : 'xlsx';
const fileName = contentDisposition
? contentDisposition.match(/filename="(.+?)"/)?.pop() || `${currentModel.model}.${extension}`
: `${currentModel.model}.${extension}`;
link.setAttribute('download', fileName);
document.body.appendChild(link);
link.click();
// Limpiar recursos
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
// Mostrar mensaje de éxito
progressDiv.innerHTML = `
<div class="flex items-center gap-3 text-green-600">
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
</svg>
<p class="text-sm">¡Exportación completada!</p>
</div>
`;
setTimeout(() => {
progressDiv.style.opacity = '0';
progressDiv.style.transform = 'translateY(100%)';
setTimeout(() => progressDiv.remove(), 300);
}, 2000);
setShowExportSuccess(true);
} catch (error) {
console.error('Error al exportar:', error);
alert(error.message || 'Error al exportar el modelo. Por favor intente nuevamente.');
@@ -194,115 +253,334 @@ export default function Reports() {
}
};
const renderFields = () => (
<div className="mb-4 flex flex-col lg:flex-row gap-4">
{/* Lista de campos disponibles */}
<div className="flex-1 bg-white/95 backdrop-blur-sm rounded-xl border border-blue-100 shadow-lg p-4">
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2 mb-3">
<span className="font-bold text-blue-800 text-sm">Campos disponibles</span>
<div className="flex gap-2">
<button
className="px-3 py-1.5 text-xs bg-blue-100 text-blue-700 rounded-lg hover:bg-blue-200 font-semibold transition-all duration-200 hover:shadow-md"
onClick={includeAllFields}
>
Incluir todos
</button>
<button
className="inline-flex items-center px-3 py-1.5 bg-blue-500 text-white rounded-lg shadow-md hover:bg-blue-600 transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed text-xs font-semibold hover:shadow-lg"
onClick={() => availableSelected && addField(availableSelected)}
disabled={!availableSelected}
title="Incluir campo seleccionado"
>
<svg className="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 4v16m8-8H4" /></svg>
Incluir
</button>
const renderFields = () => {
const formatFieldName = (field) => {
return field.split('_')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
};
return (
<div className="mb-6">
<div className="bg-white rounded-xl shadow-lg overflow-hidden border border-blue-100">
{/* Header con búsqueda y contador */}
<div className="p-4 border-b border-blue-50">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-3">
<div className="h-10 w-10 bg-blue-100 rounded-lg flex items-center justify-center">
<svg className="w-6 h-6 text-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
</svg>
</div>
<div>
<h3 className="font-bold text-gray-900">Campos del reporte</h3>
<p className="text-sm text-gray-500">Selecciona los campos a incluir</p>
</div>
</div>
<span className="inline-flex items-center px-3 py-1 rounded-full bg-blue-50 text-blue-700 text-sm font-medium">
{selectedFields.length} seleccionados
</span>
</div>
{/* Panel de campos */}
<div className="flex gap-4 flex-col lg:flex-row">
{/* Panel izquierdo - Campos disponibles */}
<div className="flex-1">
<div className="flex items-center justify-between mb-2">
<h4 className="text-sm font-medium text-gray-700">Campos disponibles</h4>
<button
onClick={includeAllFields}
disabled={availableFields.length === 0}
className="inline-flex items-center px-2.5 py-1.5 text-xs font-medium rounded-md text-blue-700 bg-blue-50 hover:bg-blue-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
>
<svg className="w-4 h-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 4v16m8-8H4" />
</svg>
Incluir todos
</button>
</div>
<div className="bg-gray-50 rounded-lg p-2 max-h-80 overflow-y-auto custom-scrollbar">
{availableFields.length === 0 ? (
<div className="flex flex-col items-center justify-center py-8 text-gray-400">
<svg className="w-12 h-12 mb-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
</svg>
<p className="text-sm">Todos los campos están incluidos</p>
</div>
) : (
<div className="grid grid-cols-1 gap-1">
{availableFields.map(field => (
<div
key={field}
onClick={() => setAvailableSelected(field)}
onDoubleClick={() => addField(field)}
className={`group flex items-center justify-between p-2 rounded-md cursor-pointer transition-all duration-200 ${
availableSelected === field
? 'bg-blue-50 text-blue-700'
: 'hover:bg-gray-100'
}`}
>
<span className="text-sm font-medium">{formatFieldName(field)}</span>
<button
onClick={(e) => {
e.stopPropagation();
addField(field);
}}
className="opacity-0 group-hover:opacity-100 p-1 rounded-md hover:bg-blue-100 text-blue-600 transition-all duration-200"
>
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 4v16m8-8H4" />
</svg>
</button>
</div>
))}
</div>
)}
</div>
</div>
{/* Panel derecho - Campos seleccionados */}
<div className="flex-1">
<div className="flex items-center justify-between mb-2">
<h4 className="text-sm font-medium text-gray-700">Campos incluidos</h4>
<button
onClick={removeAllFields}
disabled={selectedFields.length === 0}
className="inline-flex items-center px-2.5 py-1.5 text-xs font-medium rounded-md text-red-700 bg-red-50 hover:bg-red-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
>
<svg className="w-4 h-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
Quitar todos
</button>
</div>
<div className="bg-gray-50 rounded-lg p-2 max-h-80 overflow-y-auto custom-scrollbar">
{selectedFields.length === 0 ? (
<div className="flex flex-col items-center justify-center py-8 text-gray-400">
<svg className="w-12 h-12 mb-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 9v3m0 0v3m0-3h3m-3 0H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<p className="text-sm">No hay campos seleccionados</p>
</div>
) : (
<div className="grid grid-cols-1 gap-1">
{selectedFields.map(field => (
<div
key={field}
className={`group flex items-center justify-between p-2 rounded-md cursor-pointer transition-all duration-200 ${
includedSelected === field
? 'bg-red-50 text-red-700'
: 'hover:bg-gray-100'
}`}
onClick={() => setIncludedSelected(field)}
onDoubleClick={() => removeField(field)}
>
<span className="text-sm font-medium">{formatFieldName(field)}</span>
<button
onClick={(e) => {
e.stopPropagation();
removeField(field);
}}
className="opacity-0 group-hover:opacity-100 p-1 rounded-md hover:bg-red-100 text-red-600 transition-all duration-200"
>
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
))}
</div>
)}
</div>
</div>
</div>
</div>
<ul className="max-h-56 overflow-y-auto divide-y divide-blue-50">
{availableFields.length === 0 && (
<li className="text-gray-400 text-xs py-2">Todos los campos incluidos</li>
)}
{availableFields.map(field => (
<li
key={field}
className={`px-2 py-1 cursor-pointer rounded transition-all duration-100 select-none ${availableSelected === field ? 'bg-blue-100 text-blue-700' : 'hover:bg-blue-50'}`}
onClick={() => setAvailableSelected(field)}
onDoubleClick={() => addField(field)}
tabIndex={0}
>
{field}
</li>
))}
</ul>
</div>
{/* Lista de campos seleccionados */}
<div className="flex-1 bg-white/95 backdrop-blur-sm rounded-xl border border-green-100 shadow-lg p-4">
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2 mb-3">
<span className="font-bold text-green-800 text-sm">Campos incluidos</span>
<div className="flex gap-2">
<button
className="px-3 py-1.5 text-xs bg-green-100 text-green-700 rounded-lg hover:bg-green-200 font-semibold transition-all duration-200 hover:shadow-md disabled:opacity-40 disabled:cursor-not-allowed"
onClick={removeAllFields}
disabled={selectedFields.length === 0}
>
Quitar todos
</button>
<button
className="px-3 py-1.5 text-xs bg-green-100 text-green-700 rounded-lg hover:bg-green-200 font-semibold transition-all duration-200 hover:shadow-md disabled:opacity-40 disabled:cursor-not-allowed"
onClick={removeSelectedField}
disabled={!includedSelected}
>
Quitar
</button>
</div>
</div>
<ul className="max-h-56 overflow-y-auto divide-y divide-green-50">
{selectedFields.length === 0 && (
<li className="text-gray-400 text-xs py-2">No hay campos seleccionados</li>
)}
{selectedFields.map(field => (
<li
key={field}
className={`px-2 py-1 cursor-pointer rounded transition-all duration-100 select-none ${includedSelected === field ? 'bg-green-100 text-green-700' : 'hover:bg-green-50'}`}
onClick={() => setIncludedSelected(field)}
onDoubleClick={() => removeField(field)}
tabIndex={0}
title="Doble click para quitar"
>
{field}
</li>
))}
</ul>
<style jsx>{`
.custom-scrollbar::-webkit-scrollbar {
width: 4px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: #f3f4f6;
border-radius: 2px;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 2px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: #94a3b8;
}
`}</style>
</div>
</div>
);
);
};
const renderFilters = () => (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 mb-4 bg-gradient-to-br from-blue-50/60 to-blue-100/30 p-4 sm:p-5 rounded-xl border border-blue-100 shadow-lg backdrop-blur-sm">
{Object.entries(currentModel.filters).map(([key, value]) => (
<div key={key} className="flex flex-col">
<label className="block text-xs font-semibold text-blue-800 mb-1.5">{key}</label>
<input
type="text"
value={filters[key] || ''}
onChange={(e) => setFilters(prev => ({
...prev,
[key]: e.target.value
}))}
className="w-full border border-blue-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-400 focus:border-blue-400 bg-white/90 shadow-md transition-all duration-200 hover:shadow-lg"
/>
<div className="mb-4">
<div className="bg-white rounded-xl shadow-lg overflow-hidden border border-blue-100">
<div className="p-4 border-b border-blue-50">
<div className="flex items-center gap-3">
<div className="h-10 w-10 bg-blue-100 rounded-lg flex items-center justify-center">
<svg className="w-6 h-6 text-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z" />
</svg>
</div>
<div>
<h3 className="font-bold text-gray-900">Filtros de búsqueda</h3>
<p className="text-sm text-gray-500">Refina los resultados del reporte</p>
</div>
</div>
</div>
))}
<div className="p-4">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{Object.entries(currentModel.filters).map(([key, value]) => {
const label = key.split('_').map(word =>
word.charAt(0).toUpperCase() + word.slice(1)
).join(' ');
const isDate = key.toLowerCase().includes('fecha');
return (
<div key={key} className="group">
<label className="block text-sm font-medium text-gray-700 mb-1">
{label}
</label>
<div className="relative rounded-lg shadow-sm">
<input
type={isDate ? "date" : "text"}
value={filters[key] || ''}
onChange={(e) => setFilters(prev => ({
...prev,
[key]: e.target.value
}))}
className="block w-full rounded-lg border-gray-300 pl-3 pr-10 py-2.5 text-gray-900 placeholder-gray-500
focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm
transition-all duration-200 bg-white"
placeholder={`Buscar por ${label.toLowerCase()}`}
/>
<div className="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none">
{isDate ? (
<svg className="h-5 w-5 text-gray-400 group-focus-within:text-blue-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
) : (
<svg className="h-5 w-5 text-gray-400 group-focus-within:text-blue-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
)}
</div>
</div>
</div>
);
})}
</div>
</div>
</div>
</div>
);
// Contenido de cada pestaña
const tabContents = {
pedimentos: (
<div className="p-6">
<h2 className="text-xl font-bold mb-2 text-blue-900">Generar reporte de pedimentos cargados</h2>
<p className="mb-4 text-gray-700">Aquí puedes generar y descargar el reporte de pedimentos cargados.</p>
{/* Aquí va la lógica y UI específica para pedimentos */}
<div className="p-6 bg-white/95 rounded-2xl shadow-xl border border-blue-100">
<div className="relative mb-8">
<div className="absolute -left-2 -top-2 w-12 h-12 bg-gradient-to-br from-blue-600 to-blue-700 rounded-2xl shadow-lg flex items-center justify-center transform -rotate-6">
<svg className="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
</div>
<div className="pl-12">
<h2 className="text-2xl font-bold text-gray-900">Generar reporte de pedimentos</h2>
<p className="text-gray-600 mt-1">Selecciona los campos y ajusta los filtros para generar el reporte.</p>
</div>
</div>
<div className="mb-6">
<label className="block text-xs font-semibold text-blue-800 mb-2">Formato de Exportación</label>
<div className="flex gap-3">
<button
onClick={() => setExportFormat('excel')}
className={`flex-1 py-2.5 rounded-lg transition-all duration-200 border ${
exportFormat === 'excel'
? 'bg-gradient-to-br from-green-50 to-green-100 border-green-200 shadow-lg shadow-green-100 scale-[1.02]'
: 'bg-white border-gray-200 hover:bg-gray-50'
}`}
>
<div className="flex items-center justify-center gap-2">
<svg className={`w-5 h-5 ${exportFormat === 'excel' ? 'text-green-600' : 'text-gray-500'}`} viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.5" d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6z"/>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.5" d="M14 2v6h6M8 13h8M8 17h8M8 9h2"/>
</svg>
<span className={exportFormat === 'excel' ? 'font-semibold text-green-700' : 'text-gray-700'}>Excel</span>
</div>
</button>
<button
onClick={() => setExportFormat('csv')}
className={`flex-1 py-2.5 rounded-lg transition-all duration-200 border ${
exportFormat === 'csv'
? 'bg-gradient-to-br from-blue-50 to-blue-100 border-blue-200 shadow-lg shadow-blue-100 scale-[1.02]'
: 'bg-white border-gray-200 hover:bg-gray-50'
}`}
>
<div className="flex items-center justify-center gap-2">
<svg className={`w-5 h-5 ${exportFormat === 'csv' ? 'text-blue-600' : 'text-gray-500'}`} viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.5" d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6z"/>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.5" d="M14 2v6h6M10 9v10M14 9v10M6 9v10"/>
</svg>
<span className={exportFormat === 'csv' ? 'font-semibold text-blue-700' : 'text-gray-700'}>CSV</span>
</div>
</button>
</div>
</div>
<div className="mb-6">
<h3 className="text-md font-bold text-blue-800 mb-3">Campos</h3>
{renderFields()}
</div>
<div className="mb-6">
<h3 className="text-md font-bold text-blue-800 mb-3">Filtros</h3>
{renderFilters()}
</div>
<button
onClick={handleExportModel}
disabled={isExporting}
className={`group relative w-full py-3 text-lg font-semibold ${
isExporting
? 'bg-gray-400 cursor-not-allowed'
: exportFormat === 'excel'
? 'bg-gradient-to-r from-green-500 to-green-600 hover:from-green-600 hover:to-green-700'
: 'bg-gradient-to-r from-blue-500 to-blue-600 hover:from-blue-600 hover:to-blue-700'
} text-white rounded-xl transition-all duration-300 transform hover:scale-[1.02] shadow-lg hover:shadow-xl ${
exportFormat === 'excel'
? 'shadow-green-500/20 hover:shadow-green-500/30'
: 'shadow-blue-500/20 hover:shadow-blue-500/30'
} overflow-hidden`}
>
<div className={`absolute inset-0 w-full h-full ${
isExporting
? 'bg-gradient-to-r from-gray-300/0 via-gray-300/30 to-gray-300/0'
: exportFormat === 'excel'
? 'bg-gradient-to-r from-green-400/0 via-green-400/30 to-green-400/0'
: 'bg-gradient-to-r from-blue-400/0 via-blue-400/30 to-blue-400/0'
} skeleton-animation`}></div>
<span className="relative inline-flex items-center justify-center gap-2 px-4">
{isExporting ? (
<svg className="animate-spin h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
) : (
<svg className="w-5 h-5 transform group-hover:scale-110 transition-transform duration-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
)}
<span>{isExporting ? 'Generando archivo...' : `Generar y descargar ${exportFormat.toUpperCase()}`}</span>
</span>
</button>
</div>
),
datastage: (
@@ -318,24 +596,65 @@ export default function Reports() {
<p className="text-gray-600 mt-1">Selecciona el modelo, revisa los campos y ajusta los filtros para generar el reporte.</p>
</div>
</div>
<div className="mb-6">
<label className="block text-xs font-semibold text-blue-800 mb-2">Modelo</label>
<div className="relative">
<select
value={selectedModel}
onChange={e => setSelectedModel(e.target.value)}
className="w-full border border-blue-200 rounded-lg px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-400 focus:border-blue-400 bg-white/90 shadow-md appearance-none pr-10 transition-all duration-200 hover:shadow-lg"
>
{datastageModels.map(m => (
<option key={m.model} value={m.model} className="py-1">
{m.model} - {m.name}
</option>
))}
</select>
<div className="absolute inset-y-0 right-0 flex items-center px-2 pointer-events-none">
<svg className="w-4 h-4 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 9l-7 7-7-7" />
</svg>
<div className="mb-6 space-y-4">
<div>
<label className="block text-xs font-semibold text-blue-800 mb-2">Modelo</label>
<div className="relative">
<select
value={selectedModel}
onChange={e => setSelectedModel(e.target.value)}
className="w-full border border-blue-200 rounded-lg px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-400 focus:border-blue-400 bg-white/90 shadow-md appearance-none pr-10 transition-all duration-200 hover:shadow-lg"
>
{datastageModels.map(m => (
<option key={m.model} value={m.model} className="py-1">
{m.model} - {m.name}
</option>
))}
</select>
<div className="absolute inset-y-0 right-0 flex items-center px-2 pointer-events-none">
<svg className="w-4 h-4 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 9l-7 7-7-7" />
</svg>
</div>
</div>
</div>
<div>
<label className="block text-xs font-semibold text-blue-800 mb-2">Formato de Exportación</label>
<div className="flex gap-3">
<button
onClick={() => setExportFormat('excel')}
className={`flex-1 py-2.5 rounded-lg transition-all duration-200 border ${
exportFormat === 'excel'
? 'bg-gradient-to-br from-green-50 to-green-100 border-green-200 shadow-lg shadow-green-100 scale-[1.02]'
: 'bg-white border-gray-200 hover:bg-gray-50'
}`}
>
<div className="flex items-center justify-center gap-2">
<svg className={`w-5 h-5 ${exportFormat === 'excel' ? 'text-green-600' : 'text-gray-500'}`} viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.5" d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6z"/>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.5" d="M14 2v6h6M8 13h8M8 17h8M8 9h2"/>
</svg>
<span className={exportFormat === 'excel' ? 'font-semibold text-green-700' : 'text-gray-700'}>Excel</span>
</div>
</button>
<button
onClick={() => setExportFormat('csv')}
className={`flex-1 py-2.5 rounded-lg transition-all duration-200 border ${
exportFormat === 'csv'
? 'bg-gradient-to-br from-blue-50 to-blue-100 border-blue-200 shadow-lg shadow-blue-100 scale-[1.02]'
: 'bg-white border-gray-200 hover:bg-gray-50'
}`}
>
<div className="flex items-center justify-center gap-2">
<svg className={`w-5 h-5 ${exportFormat === 'csv' ? 'text-blue-600' : 'text-gray-500'}`} viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.5" d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6z"/>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.5" d="M14 2v6h6M10 9v10M14 9v10M6 9v10"/>
</svg>
<span className={exportFormat === 'csv' ? 'font-semibold text-blue-700' : 'text-gray-700'}>CSV</span>
</div>
</button>
</div>
</div>
</div>
@@ -353,13 +672,21 @@ export default function Reports() {
className={`group relative w-full py-3 text-lg font-semibold ${
isExporting
? 'bg-gray-400 cursor-not-allowed'
: 'bg-gradient-to-r from-green-500 to-green-600 hover:from-green-600 hover:to-green-700'
} text-white rounded-xl transition-all duration-300 transform hover:scale-[1.02] shadow-lg hover:shadow-xl shadow-green-500/20 hover:shadow-green-500/30 overflow-hidden`}
: exportFormat === 'excel'
? 'bg-gradient-to-r from-green-500 to-green-600 hover:from-green-600 hover:to-green-700'
: 'bg-gradient-to-r from-blue-500 to-blue-600 hover:from-blue-600 hover:to-blue-700'
} text-white rounded-xl transition-all duration-300 transform hover:scale-[1.02] shadow-lg hover:shadow-xl ${
exportFormat === 'excel'
? 'shadow-green-500/20 hover:shadow-green-500/30'
: 'shadow-blue-500/20 hover:shadow-blue-500/30'
} overflow-hidden`}
>
<div className={`absolute inset-0 w-full h-full ${
isExporting
? 'bg-gradient-to-r from-gray-300/0 via-gray-300/30 to-gray-300/0'
: 'bg-gradient-to-r from-green-400/0 via-green-400/30 to-green-400/0'
: exportFormat === 'excel'
? 'bg-gradient-to-r from-green-400/0 via-green-400/30 to-green-400/0'
: 'bg-gradient-to-r from-blue-400/0 via-blue-400/30 to-blue-400/0'
} skeleton-animation`}></div>
<span className="relative inline-flex items-center justify-center gap-2 px-4">
{isExporting ? (
@@ -372,7 +699,7 @@ export default function Reports() {
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
)}
<span>{isExporting ? 'Generando archivo...' : 'Generar y descargar CSV'}</span>
<span>{isExporting ? 'Generando archivo...' : `Generar y descargar ${exportFormat.toUpperCase()}`}</span>
</span>
</button>