From 2aab5c8fdcdcca5961644788264a7d064a32f1b9 Mon Sep 17 00:00:00 2001 From: AlexeerCT Date: Sun, 8 Mar 2026 01:15:08 -0600 Subject: [PATCH] feat: add optional seed data functionality to init_first_time.sh - Implemented argument parsing to allow for optional loading of example data with --seed-data flag. - Added functions to seed example data for customs brokers, clients and providers, packages, classes, parts, pedimentos, and invoices. - Enhanced user feedback during the seeding process with detailed output of the seeded data counts. - Updated usage instructions in the script header to reflect the new functionality. --- frontend/package.json | 1 + frontend/pnpm-lock.yaml | 16 + .../components/dashboard/activity-feed.svelte | 94 +++--- .../components/dashboard/chart-card.svelte | 53 ++- .../components/dashboard/donut-chart.svelte | 158 +++++---- .../lib/components/dashboard/kpi-card.svelte | 62 ++-- .../components/dashboard/trend-chart.svelte | 245 +++++++++++--- frontend/src/routes/dashboard/+page.svelte | 293 ++++++++++------- scripts/init_first_time.sh | 304 +++++++++++++++++- 9 files changed, 900 insertions(+), 326 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index e7cd1887..068aeeab 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -59,6 +59,7 @@ "dependencies": { "@types/dompurify": "^3.2.0", "@types/marked": "^6.0.0", + "chart.js": "^4.5.1", "dompurify": "^3.0.9", "keycloak-js": "^26.2.1", "lucide-svelte": "^0.553.0", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 4933f90b..aca29481 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: '@types/marked': specifier: ^6.0.0 version: 6.0.0 + chart.js: + specifier: ^4.5.1 + version: 4.5.1 dompurify: specifier: ^3.0.9 version: 3.3.1 @@ -415,6 +418,9 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@kurkle/color@0.3.4': + resolution: {integrity: sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==} + '@lix-js/sdk@0.4.7': resolution: {integrity: sha512-pRbW+joG12L0ULfMiWYosIW0plmW4AsUdiPCp+Z8rAsElJ+wJ6in58zhD3UwUcd4BNcpldEGjg6PdA7e0RgsDQ==} engines: {node: '>=18'} @@ -993,6 +999,10 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + chart.js@4.5.1: + resolution: {integrity: sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==} + engines: {pnpm: '>=8'} + check-error@2.1.1: resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} engines: {node: '>= 16'} @@ -2289,6 +2299,8 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@kurkle/color@0.3.4': {} + '@lix-js/sdk@0.4.7': dependencies: '@lix-js/server-protocol-schema': 0.1.1 @@ -2852,6 +2864,10 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + chart.js@4.5.1: + dependencies: + '@kurkle/color': 0.3.4 + check-error@2.1.1: {} chokidar@4.0.3: diff --git a/frontend/src/lib/components/dashboard/activity-feed.svelte b/frontend/src/lib/components/dashboard/activity-feed.svelte index fde3b9b4..9f501382 100644 --- a/frontend/src/lib/components/dashboard/activity-feed.svelte +++ b/frontend/src/lib/components/dashboard/activity-feed.svelte @@ -14,9 +14,10 @@ interface Props { activities: ActivityItem[]; + class?: string; } - let { activities }: Props = $props(); + let { activities, class: cls = '' }: Props = $props(); const icons = { invoice: FileText, @@ -39,14 +40,24 @@ } function formatDate(dateStr: string): string { - const date = new Date(dateStr); + // Normalizar: si el string no tiene info de zona horaria, asumir UTC agregando 'Z' + const normalized = /[Z+\-]\d*$/.test(dateStr.trim()) ? dateStr : dateStr + 'Z'; + const date = new Date(normalized); const now = new Date(); const diffMs = now.getTime() - date.getTime(); + + // Si la fecha es futura (diff negativo) o muy reciente, mostrar 'Ahora mismo' + if (diffMs < 0) { + return 'Ahora mismo'; + } + const diffMins = Math.floor(diffMs / 60000); const diffHours = Math.floor(diffMs / 3600000); const diffDays = Math.floor(diffMs / 86400000); - if (diffMins < 60) { + if (diffMins < 1) { + return 'Ahora mismo'; + } else if (diffMins < 60) { return `Hace ${diffMins} min`; } else if (diffHours < 24) { return `Hace ${diffHours}h`; @@ -58,48 +69,55 @@ } - - - Actividad Reciente - Últimas operaciones registradas en el sistema + + +
+
+ + + Actividad Reciente + + Últimas operaciones registradas en el sistema +
+ {#if activities.length > 0} + {activities.length} registros + {/if} +
{#if activities.length === 0} -
- -

No hay actividad reciente

+
+ +

No hay actividad reciente

{:else} -
- {#each activities as activity} - {@const Icon = getIcon(activity.type)} -
-
-
- +
+ +
+ +
+ {#each activities as activity, idx} + {@const Icon = getIcon(activity.type)} +
+
+
+ +
+
+
+
+

{activity.title}

+ + {formatDate(activity.timestamp)} + +
+ {#if activity.description} +

{activity.description}

+ {/if}
-
-
-

{activity.title}

- - {formatDate(activity.timestamp)} - -
- {#if activity.description} -

{activity.description}

- {/if} - {#if activity.status} - - {activity.status} - - {/if} -
-
- {/each} + {/each} +
{/if} diff --git a/frontend/src/lib/components/dashboard/chart-card.svelte b/frontend/src/lib/components/dashboard/chart-card.svelte index 529f10bb..7080bb2f 100644 --- a/frontend/src/lib/components/dashboard/chart-card.svelte +++ b/frontend/src/lib/components/dashboard/chart-card.svelte @@ -1,41 +1,62 @@ - - - {title} + + + {title} {#if description} {description} {/if} {#if data.length === 0} -
No hay datos disponibles
+
+

No hay datos disponibles

+
{:else if type === 'bar'}
- {#each data as item} -
-
- {item.label} - {item.value.toLocaleString()} + {#each data as item, i} +
+
+ {i + 1} + {item.label} + {item.value.toLocaleString()}
-
+
@@ -43,10 +64,10 @@ {/each}
{:else if type === 'pie'} -
- {#each data as item} +
+ {#each data as item, i}
-
+
{item.label}
{item.value.toLocaleString()}
diff --git a/frontend/src/lib/components/dashboard/donut-chart.svelte b/frontend/src/lib/components/dashboard/donut-chart.svelte index 64f6c17f..2635f565 100644 --- a/frontend/src/lib/components/dashboard/donut-chart.svelte +++ b/frontend/src/lib/components/dashboard/donut-chart.svelte @@ -1,103 +1,135 @@ - - - {title} + + +
+
+ + + {title} + + Desglose por tipo de operación +
+ {#if total > 0} + {total.toLocaleString()} ops + {/if} +
- + + {#if data.length === 0} -
No hay datos disponibles
+
+
+ +
+
+

Sin datos disponibles

+

Las operaciones aparecerán aquí una vez registradas

+
+
{:else} -
- -
- - {#each donutSegments() as segment, i} +
+ + +
+ + + {#each donutSlices() as s} {/each} - -
-
-
{total.toLocaleString()}
-
Total
-
+
+ {total.toLocaleString()} + total
- -
- {#each segments as segment, i} -
-
-
- {segment.label} + +
+ {#each segments as s} +
+
+
+ + {s.label} +
+
+ {s.value.toLocaleString()} + {s.pct.toFixed(1)}% +
-
- - {segment.value.toLocaleString()} - - - ({segment.percentage.toFixed(1)}%) - +
+
{/each}
+
{/if} diff --git a/frontend/src/lib/components/dashboard/kpi-card.svelte b/frontend/src/lib/components/dashboard/kpi-card.svelte index 0c0db870..ae0780a5 100644 --- a/frontend/src/lib/components/dashboard/kpi-card.svelte +++ b/frontend/src/lib/components/dashboard/kpi-card.svelte @@ -1,5 +1,4 @@ - - - - {metric.label} - - {#if Icon} +
+ {#if Icon} +
- {/if} - - -
- {metric.value.toLocaleString()} - {#if metric.unit} - {metric.unit} - {/if}
- {#if metric.percentage_change !== undefined && TrendIcon} -
- - + {/if} + +
+

{metric.label}

+
+ + {metric.value.toLocaleString()}{#if metric.unit}{metric.unit}{/if} + + {#if metric.percentage_change !== undefined && TrendIcon && metric.trend} + + {Math.abs(metric.percentage_change).toFixed(1)}% - vs mes anterior -
- {/if} - - + {/if} +
+
+
diff --git a/frontend/src/lib/components/dashboard/trend-chart.svelte b/frontend/src/lib/components/dashboard/trend-chart.svelte index e9dd978e..288acd4c 100644 --- a/frontend/src/lib/components/dashboard/trend-chart.svelte +++ b/frontend/src/lib/components/dashboard/trend-chart.svelte @@ -1,71 +1,216 @@ - - - Tendencia de Operaciones - Evolución mensual de facturas y pedimentos - - - {#if monthlyData.length === 0} -
-

No hay datos disponibles

-
- {:else} - -
- {#each monthlyData as point, i} -
- -
-
- -
- {point.value.toLocaleString()} -
-
-
- - {point.label} -
- {/each} + + +
+
+ + + Tendencia de Operaciones + + Evolución mensual de operaciones
- -
-
-
- {monthlyData.reduce((sum, d) => sum + d.value, 0).toLocaleString()} -
-
Total
+ {#if monthlyData.length >= 2} + {@const pct = trendPct()} +
+ {#if pct > 0} + +{pct}% + {:else if pct < 0} + {pct}% + {:else} + 0% + {/if} + vs mes ant. +
+ {/if} +
+ + + + {#if monthlyData.length === 0} +
+
+
-
- {Math.round( - monthlyData.reduce((sum, d) => sum + d.value, 0) / monthlyData.length - ).toLocaleString()} -
-
Promedio
+

Sin datos disponibles

+

Los datos aparecerán aquí una vez registrados

+
+ {:else if monthlyData.length === 1} +
-
{maxValue.toLocaleString()}
-
Máximo
+
{monthlyData[0].value.toLocaleString()}
+
+ operaciones en {monthlyData[0].label} +
+
+
+ + La gráfica aparecerá con más de un mes de datos +
+
+ {:else} + +
+ +
+ + +
+
+
{total.toLocaleString()}
+
Total
+
+
+
{average.toLocaleString()}
+
Promedio / mes
+
+
+
{maxValue.toLocaleString()}
+
Máximo
{/if} diff --git a/frontend/src/routes/dashboard/+page.svelte b/frontend/src/routes/dashboard/+page.svelte index 1c28eec3..7922ab1b 100644 --- a/frontend/src/routes/dashboard/+page.svelte +++ b/frontend/src/routes/dashboard/+page.svelte @@ -63,26 +63,35 @@
{#snippet headerSection()}
-
-

+
+
+ Dashboard + {#if stats?.company_name} + / + {stats.company_name} + {/if} +
+

{greeting()}, equipo.

-

+

{#if stats?.company_name} - {stats.company_name} - Anexos 22/24/30 + + + Resumen operativo — Anexos 22/24/30 + {:else} Sistema de gestión de comercio exterior {/if}

- -
@@ -100,133 +109,171 @@ {/if} {#if loading} -
+ +
{#each Array(6) as _} +
+
+
+
+
+
+
+ {/each} +
+ +
+ {#each Array(2) as _} - -
+ +
+
-
+
{/each}
{:else if stats} - -
- - - - - - -
+ +
- -
- - -
- - -
- - -
- - -
- - - - - -
-
-
- {stats.total_invoices.value + stats.total_pedimentos.value} -
-
Total de Documentos
-
-
-
- {stats.total_clients.value + stats.total_providers.value} -
-
Total de Contactos
-
-
-
{stats.active_items.value}
-
Items Activos
-
+ +
+ +
+
+
+ {(stats.total_invoices.value + stats.total_pedimentos.value).toLocaleString()}
- - +
Total Documentos
+
+
+
+ {(stats.total_clients.value + stats.total_providers.value).toLocaleString()} +
+
Total Contactos
+
+
+
+ {stats.active_items.value.toLocaleString()} +
+
Items Activos
+
+ +
{/if}
diff --git a/scripts/init_first_time.sh b/scripts/init_first_time.sh index d9f09601..e4fb6bd9 100755 --- a/scripts/init_first_time.sh +++ b/scripts/init_first_time.sh @@ -12,6 +12,11 @@ # 6. Relación usuario-tenant en tabla user_tenants # 7. Actualización del tenant_id del usuario con el valor real # 8. Licencia Enterprise para el tenant (ilimitada, 1 año de vigencia) +# 9. [OPCIONAL] Datos iniciales de ejemplo si se pasa --seed-data +# +# Uso: +# ./init_first_time.sh # Solo configuración básica +# ./init_first_time.sh --seed-data # Configuración + datos de ejemplo # # Requisitos: # - Keycloak corriendo en http://localhost:8080 @@ -19,8 +24,6 @@ # - Base de datos anexo76_core creada # - jq instalado (para procesamiento JSON) # -# Puertos: Keycloak en 18080/19000, frontend en 15173, API en 18000. -# # Nota: El atributo tenant_id se crea con valor inicial "1" y luego # se actualiza con el ID real del tenant creado en PostgreSQL. ############################################################################### @@ -30,6 +33,22 @@ set -euo pipefail # Modo strict: exit on error, undefined vars, pipe failures # Trap para cleanup en caso de error trap 'echo -e "\n${RED}✗ Error en línea $LINENO. Script abortado.${NC}" >&2' ERR +# Parsear argumentos +SEED_DATA=false +while [[ $# -gt 0 ]]; do + case $1 in + --seed-data) + SEED_DATA=true + shift + ;; + *) + echo "Uso: $0 [--seed-data]" + echo " --seed-data: Carga datos de ejemplo en las tablas" + exit 1 + ;; + esac +done + # Colores para output RED='\033[0;31m' GREEN='\033[0;32m' @@ -125,7 +144,265 @@ create_tenant_mapper() { fi } -# Variables de configuración (puertos con prefijo 1 hardcodeados) +############################################################################### +# Funciones de seed data +############################################################################### + +# Insertar datos de ejemplo para customs_brokers +seed_customs_brokers() { + echo " → Insertando customs brokers..." + exec_pg_sql " + INSERT INTO a76.customs_brokers (tenant_id, company_id, type, broker_key, name, address, postal_code, city, state, phone, email, country, tax_id, license, company, contact, created_at, updated_at) + VALUES + (${TENANT_ID}, ${COMPANY_ID}, 'persona', 'CB001', 'Agente Aduanal García', 'Av. Reforma 123', '01000', 'Ciudad de México', 'CDMX', '5555555555', 'garcia@aduanas.com', 'MEX', 'GAAR800101ABC', '1234', 'García y Asociados', 'Juan García', now(), now()), + (${TENANT_ID}, ${COMPANY_ID}, 'persona', 'CB002', 'Agente Aduanal López', 'Blvd. Díaz Ordaz 456', '22000', 'Tijuana', 'BC', '6641234567', 'lopez@customs.com', 'MEX', 'LOPL750505XYZ', '2345', 'López Customs', 'María López', now(), now()), + (${TENANT_ID}, ${COMPANY_ID}, 'persona', 'CB003', 'Agente Aduanal Martínez', 'Calle Industria 789', '45000', 'Guadalajara', 'JAL', '3339876543', 'martinez@broker.com', 'MEX', 'MARM850315DEF', '3456', 'Martínez Brokerage', 'Pedro Martínez', now(), now()) + ON CONFLICT (broker_key, tenant_id, company_id) DO NOTHING; + " >/dev/null 2>&1 +} + +# Insertar datos de ejemplo para clients_and_providers +seed_clients_and_providers() { + echo " → Insertando clientes y proveedores..." + exec_pg_sql " + INSERT INTO a76.clients_and_providers (tenant_id, company_id, type_nat_foreign, name, short_name, rfc, client_or_provider, web_key, is_active, created_at, updated_at) + VALUES + (${TENANT_ID}, ${COMPANY_ID}, 'N', 'Proveedor Tecnológico SA de CV', 'PROVTECH', 'PTE901201ABC', 'BOTH', 'PROV001', true, now(), now()), + (${TENANT_ID}, ${COMPANY_ID}, 'N', 'Cliente Industrial del Norte SA', 'CINORTE', 'CIN850615XYZ', 'BOTH', 'CLI001', true, now(), now()), + (${TENANT_ID}, ${COMPANY_ID}, 'E', 'Global Supplies Inc', 'GLOBSUP', 'GSI123456789', 'BOTH', 'BOTH001', true, now(), now()), + (${TENANT_ID}, ${COMPANY_ID}, 'N', 'Manufacturas del Bajío SA', 'MANBAJIO', 'MDB920310DEF', 'BOTH', 'CLI002', true, now(), now()) + RETURNING id; + " >/dev/null 2>&1 +} + +# Insertar datos de ejemplo para packages +seed_packages() { + echo " → Insertando tipos de paquete..." + exec_pg_sql " + INSERT INTO a76.packages (tenant_id, company_id, key, description_es, description_en, weight_unit, plurals, plural_in, code_ace, code_aamex, created_at, updated_at) + VALUES + (${TENANT_ID}, ${COMPANY_ID}, 'PK01', 'Caja de Cartón', 'Cardboard Box', 0.5, 'CAJS', 'BOXS', 'CB01', 'CAJA001', now(), now()), + (${TENANT_ID}, ${COMPANY_ID}, 'PK02', 'Pallet de Madera', 'Wooden Pallet', 15.0, 'PLTS', 'PLTS', 'WP01', 'PALL001', now(), now()), + (${TENANT_ID}, ${COMPANY_ID}, 'PK03', 'Tambor Metálico', 'Metal Drum', 10.0, 'TMBS', 'DRMS', 'MD01', 'TAMB001', now(), now()), + (${TENANT_ID}, ${COMPANY_ID}, 'PK04', 'Contenedor', 'Container', 2000.0, 'CONT', 'CONT', 'CT01', 'CONT001', now(), now()) + ON CONFLICT (tenant_id, company_id, key) DO NOTHING; + " >/dev/null 2>&1 +} + +# Insertar datos de ejemplo para classes +seed_classes() { + echo " → Insertando clases..." + exec_pg_sql "INSERT INTO a76.classes (tenant_id, company_id, class_code, description_es, description_en, material_key, unit_of_measure, fraction, us_fraction, created_at, updated_at) VALUES (${TENANT_ID}, ${COMPANY_ID}, 'CLS001', 'Componentes Electrónicos', 'Electronic Components', 'MP', 'PZA', '8542.31.01', '8542.31.0000', now(), now()), (${TENANT_ID}, ${COMPANY_ID}, 'CLS002', 'Partes Automotrices', 'Automotive Parts', 'MP', 'KGS', '8708.29.99', '8708.29.9900', now(), now()), (${TENANT_ID}, ${COMPANY_ID}, 'CLS003', 'Textiles y Telas', 'Textiles and Fabrics', 'MP', 'MT', '5407.20.01', '5407.20.0100', now(), now()), (${TENANT_ID}, ${COMPANY_ID}, 'CLS004', 'Equipo de Computación', 'Computer Equipment', 'MP', 'PZA', '8471.30.01', '8471.30.0100', now(), now()) ON CONFLICT (tenant_id, company_id, class_code) DO NOTHING;" >/dev/null 2>&1 +} + +# Insertar datos de ejemplo para parts +seed_parts() { + echo " → Insertando partes/componentes..." + + # Obtener un client_id para asociar las partes + local client_id + client_id=$(exec_pg_sql "SELECT id FROM a76.clients_and_providers WHERE tenant_id = ${TENANT_ID} AND company_id = ${COMPANY_ID} LIMIT 1;" | xargs) + + if [ -n "$client_id" ]; then + exec_pg_sql "INSERT INTO a76.parts (tenant_id, company_id, client_id, part_number, description_spanish, description_english, part_class, currency_key, unit_of_measure, unit_cost, fraction, us_fraction, created_at, updated_at) VALUES (${TENANT_ID}, ${COMPANY_ID}, ${client_id}, 'PART-001', 'Microcontrolador ARM Cortex-M4', 'ARM Cortex-M4 Microcontroller', 'CLS001', 'USD', 'PZA', 15.50, '8542.31.01', '8542.31.0000', now(), now()), (${TENANT_ID}, ${COMPANY_ID}, ${client_id}, 'PART-002', 'Filtro de Aceite Automotriz', 'Automotive Oil Filter', 'CLS002', 'USD', 'PZA', 8.75, '8421.23.01', '8421.23.0100', now(), now()), (${TENANT_ID}, ${COMPANY_ID}, ${client_id}, 'PART-003', 'Tela de Algodón para Tapicería', 'Cotton Upholstery Fabric', 'CLS003', 'USD', 'MT', 12.00, '5208.31.01', '5208.31.0100', now(), now()), (${TENANT_ID}, ${COMPANY_ID}, ${client_id}, 'PART-004', 'Disco Duro SSD 500GB', '500GB SSD Hard Drive', 'CLS004', 'USD', 'PZA', 65.00, '8471.70.01', '8471.70.0100', now(), now()), (${TENANT_ID}, ${COMPANY_ID}, ${client_id}, 'PART-005', 'Sensor de Temperatura Digital', 'Digital Temperature Sensor', 'CLS001', 'USD', 'PZA', 5.25, '9025.19.01', '9025.19.0100', now(), now()) ON CONFLICT (tenant_id, company_id, part_number) DO NOTHING;" >/dev/null 2>&1 + fi +} + +# Insertar datos de ejemplo para pedimentos +seed_pedimentos() { + echo " → Insertando pedimentos..." + + # Primero obtener IDs de clientes + local client_ids + client_ids=$(exec_pg_sql "SELECT id FROM a76.clients_and_providers WHERE tenant_id = ${TENANT_ID} AND company_id = ${COMPANY_ID} AND client_or_provider IN ('CLIENT', 'BOTH') LIMIT 2;") + local client_id_1=$(echo "$client_ids" | sed -n '1p' | xargs) + local client_id_2=$(echo "$client_ids" | sed -n '2p' | xargs) + + if [ -n "$client_id_1" ]; then + exec_pg_sql "INSERT INTO a76.pedimentos (tenant_id, company_id, year, customs_office, license, pedimento_number, client_id, operation_type, pedimento_type, pedimento_code, regime, status, created_at, updated_at) VALUES (${TENANT_ID}, ${COMPANY_ID}, '24', '47', '3807', '8001234', ${client_id_1}, 'imp', 'normal', 'V1', 'ITE', 'draft', now(), now()), (${TENANT_ID}, ${COMPANY_ID}, '24', '47', '3807', '8001235', ${client_id_1}, 'exp', 'normal', 'V1', 'ETE', 'draft', now(), now()) ON CONFLICT (tenant_id, company_id, year, customs_office, license, pedimento_number) DO NOTHING;" >/dev/null 2>&1 + fi + + if [ -n "$client_id_2" ]; then + exec_pg_sql "INSERT INTO a76.pedimentos (tenant_id, company_id, year, customs_office, license, pedimento_number, client_id, operation_type, pedimento_type, pedimento_code, regime, status, created_at, updated_at) VALUES (${TENANT_ID}, ${COMPANY_ID}, '24', '47', '3807', '8001236', ${client_id_2}, 'imp', 'consolidated', 'V1', 'ITE', 'draft', now(), now()) ON CONFLICT (tenant_id, company_id, year, customs_office, license, pedimento_number) DO NOTHING;" >/dev/null 2>&1 + fi +} + +# Insertar datos de ejemplo para invoices +# Genera ~56 facturas distribuidas en los últimos 12 meses para alimentar la gráfica de tendencia +seed_invoices() { + echo " → Insertando facturas de los últimos 12 meses..." + + # ------------------------------------------------------------------------- + # 1. invoice_header — una fila por factura con fecha real en cada mes + # ------------------------------------------------------------------------- + exec_pg_sql "INSERT INTO a76.invoice_header + (tenant_id, company_id, system, operation_type, invoice_type, invoice_number, invoice_date, is_updated, created_at, updated_at) + VALUES + -- Abril 2025 (4 facturas) + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'imp', 'DEF', 'SEED-2025-04-001', '2025-04-03', false, '2025-04-03 08:00:00', '2025-04-03 08:00:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'exp', 'EXDEF', 'SEED-2025-04-002', '2025-04-11', false, '2025-04-11 10:30:00', '2025-04-11 10:30:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'imp', 'TEM', 'SEED-2025-04-003', '2025-04-18', false, '2025-04-18 14:00:00', '2025-04-18 14:00:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'exp', 'DEF', 'SEED-2025-04-004', '2025-04-25', false, '2025-04-25 09:15:00', '2025-04-25 09:15:00'), + -- Mayo 2025 (5 facturas) + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'imp', 'DEF', 'SEED-2025-05-001', '2025-05-02', false, '2025-05-02 08:00:00', '2025-05-02 08:00:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'imp', 'TEM', 'SEED-2025-05-002', '2025-05-07', false, '2025-05-07 11:00:00', '2025-05-07 11:00:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'exp', 'EXDEF', 'SEED-2025-05-003', '2025-05-14', false, '2025-05-14 13:30:00', '2025-05-14 13:30:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'imp', 'DEF', 'SEED-2025-05-004', '2025-05-20', false, '2025-05-20 09:00:00', '2025-05-20 09:00:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'exp', 'DEF', 'SEED-2025-05-005', '2025-05-28', false, '2025-05-28 16:00:00', '2025-05-28 16:00:00'), + -- Junio 2025 (3 facturas) + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'imp', 'TEM', 'SEED-2025-06-001', '2025-06-05', false, '2025-06-05 08:30:00', '2025-06-05 08:30:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'exp', 'EXDEF', 'SEED-2025-06-002', '2025-06-17', false, '2025-06-17 12:00:00', '2025-06-17 12:00:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'imp', 'DEF', 'SEED-2025-06-003', '2025-06-27', false, '2025-06-27 10:00:00', '2025-06-27 10:00:00'), + -- Julio 2025 (6 facturas) + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'imp', 'DEF', 'SEED-2025-07-001', '2025-07-02', false, '2025-07-02 08:00:00', '2025-07-02 08:00:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'exp', 'DEF', 'SEED-2025-07-002', '2025-07-07', false, '2025-07-07 10:00:00', '2025-07-07 10:00:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'imp', 'TEM', 'SEED-2025-07-003', '2025-07-11', false, '2025-07-11 09:30:00', '2025-07-11 09:30:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'exp', 'EXDEF', 'SEED-2025-07-004', '2025-07-16', false, '2025-07-16 14:00:00', '2025-07-16 14:00:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'imp', 'DEF', 'SEED-2025-07-005', '2025-07-22', false, '2025-07-22 11:00:00', '2025-07-22 11:00:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'imp', 'TEM', 'SEED-2025-07-006', '2025-07-29', false, '2025-07-29 15:00:00', '2025-07-29 15:00:00'), + -- Agosto 2025 (4 facturas) + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'exp', 'EXDEF', 'SEED-2025-08-001', '2025-08-04', false, '2025-08-04 08:00:00', '2025-08-04 08:00:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'imp', 'DEF', 'SEED-2025-08-002', '2025-08-12', false, '2025-08-12 10:30:00', '2025-08-12 10:30:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'exp', 'DEF', 'SEED-2025-08-003', '2025-08-19', false, '2025-08-19 13:00:00', '2025-08-19 13:00:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'imp', 'TEM', 'SEED-2025-08-004', '2025-08-26', false, '2025-08-26 09:00:00', '2025-08-26 09:00:00'), + -- Septiembre 2025 (5 facturas) + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'imp', 'DEF', 'SEED-2025-09-001', '2025-09-02', false, '2025-09-02 08:00:00', '2025-09-02 08:00:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'exp', 'EXDEF', 'SEED-2025-09-002', '2025-09-09', false, '2025-09-09 11:30:00', '2025-09-09 11:30:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'imp', 'TEM', 'SEED-2025-09-003', '2025-09-15', false, '2025-09-15 14:00:00', '2025-09-15 14:00:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'exp', 'DEF', 'SEED-2025-09-004', '2025-09-22', false, '2025-09-22 09:30:00', '2025-09-22 09:30:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'imp', 'DEF', 'SEED-2025-09-005', '2025-09-29', false, '2025-09-29 16:00:00', '2025-09-29 16:00:00'), + -- Octubre 2025 (7 facturas) + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'imp', 'DEF', 'SEED-2025-10-001', '2025-10-01', false, '2025-10-01 08:00:00', '2025-10-01 08:00:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'exp', 'EXDEF', 'SEED-2025-10-002', '2025-10-06', false, '2025-10-06 10:00:00', '2025-10-06 10:00:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'imp', 'TEM', 'SEED-2025-10-003', '2025-10-10', false, '2025-10-10 09:00:00', '2025-10-10 09:00:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'exp', 'DEF', 'SEED-2025-10-004', '2025-10-15', false, '2025-10-15 13:30:00', '2025-10-15 13:30:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'imp', 'DEF', 'SEED-2025-10-005', '2025-10-20', false, '2025-10-20 11:00:00', '2025-10-20 11:00:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'imp', 'TEM', 'SEED-2025-10-006', '2025-10-24', false, '2025-10-24 14:30:00', '2025-10-24 14:30:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'exp', 'EXDEF', 'SEED-2025-10-007', '2025-10-29', false, '2025-10-29 08:30:00', '2025-10-29 08:30:00'), + -- Noviembre 2025 (4 facturas) + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'imp', 'DEF', 'SEED-2025-11-001', '2025-11-04', false, '2025-11-04 09:00:00', '2025-11-04 09:00:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'exp', 'DEF', 'SEED-2025-11-002', '2025-11-12', false, '2025-11-12 11:00:00', '2025-11-12 11:00:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'imp', 'TEM', 'SEED-2025-11-003', '2025-11-19', false, '2025-11-19 14:00:00', '2025-11-19 14:00:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'exp', 'EXDEF', 'SEED-2025-11-004', '2025-11-26', false, '2025-11-26 10:00:00', '2025-11-26 10:00:00'), + -- Diciembre 2025 (3 facturas) + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'imp', 'DEF', 'SEED-2025-12-001', '2025-12-03', false, '2025-12-03 08:00:00', '2025-12-03 08:00:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'exp', 'EXDEF', 'SEED-2025-12-002', '2025-12-11', false, '2025-12-11 12:00:00', '2025-12-11 12:00:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'imp', 'TEM', 'SEED-2025-12-003', '2025-12-19', false, '2025-12-19 09:30:00', '2025-12-19 09:30:00'), + -- Enero 2026 (5 facturas) + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'imp', 'DEF', 'SEED-2026-01-001', '2026-01-07', false, '2026-01-07 08:00:00', '2026-01-07 08:00:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'exp', 'DEF', 'SEED-2026-01-002', '2026-01-13', false, '2026-01-13 10:30:00', '2026-01-13 10:30:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'imp', 'TEM', 'SEED-2026-01-003', '2026-01-17', false, '2026-01-17 13:00:00', '2026-01-17 13:00:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'exp', 'EXDEF', 'SEED-2026-01-004', '2026-01-22', false, '2026-01-22 09:00:00', '2026-01-22 09:00:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'imp', 'DEF', 'SEED-2026-01-005', '2026-01-29', false, '2026-01-29 14:30:00', '2026-01-29 14:30:00'), + -- Febrero 2026 (6 facturas) + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'imp', 'DEF', 'SEED-2026-02-001', '2026-02-03', false, '2026-02-03 08:00:00', '2026-02-03 08:00:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'exp', 'EXDEF', 'SEED-2026-02-002', '2026-02-06', false, '2026-02-06 10:00:00', '2026-02-06 10:00:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'imp', 'TEM', 'SEED-2026-02-003', '2026-02-11', false, '2026-02-11 09:30:00', '2026-02-11 09:30:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'exp', 'DEF', 'SEED-2026-02-004', '2026-02-17', false, '2026-02-17 13:00:00', '2026-02-17 13:00:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'imp', 'DEF', 'SEED-2026-02-005', '2026-02-21', false, '2026-02-21 11:00:00', '2026-02-21 11:00:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'exp', 'EXDEF', 'SEED-2026-02-006', '2026-02-26', false, '2026-02-26 15:30:00', '2026-02-26 15:30:00'), + -- Marzo 2026 (5 facturas) + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'imp', 'DEF', 'SEED-2026-03-001', '2026-03-02', false, '2026-03-02 08:00:00', '2026-03-02 08:00:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'exp', 'DEF', 'SEED-2026-03-002', '2026-03-05', false, '2026-03-05 10:00:00', '2026-03-05 10:00:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'imp', 'TEM', 'SEED-2026-03-003', '2026-03-06', false, '2026-03-06 09:00:00', '2026-03-06 09:00:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'exp', 'EXDEF', 'SEED-2026-03-004', '2026-03-07', false, '2026-03-07 14:00:00', '2026-03-07 14:00:00'), + (${TENANT_ID}, ${COMPANY_ID}, 'fixed_asset', 'imp', 'DEF', 'SEED-2026-03-005', '2026-03-08', false, '2026-03-08 08:30:00', '2026-03-08 08:30:00') + ON CONFLICT DO NOTHING;" >/dev/null 2>&1 + + # ------------------------------------------------------------------------- + # 2. invoice_financials — INSERT ... SELECT desde invoice_header + # ------------------------------------------------------------------------- + exec_pg_sql "INSERT INTO a76.invoice_financials + (tenant_id, company_id, invoice_id, currency, exchange_rate, created_at, updated_at) + SELECT + ih.tenant_id, + ih.company_id, + ih.id, + CASE WHEN ih.operation_type = 'imp' THEN 'foreign' ELSE 'local' END, + CASE WHEN ih.operation_type = 'imp' THEN 17.50 ELSE 1.00 END, + ih.created_at, + ih.updated_at + FROM a76.invoice_header ih + WHERE ih.tenant_id = ${TENANT_ID} + AND ih.company_id = ${COMPANY_ID} + AND ih.invoice_number LIKE 'SEED-%' + ON CONFLICT DO NOTHING;" >/dev/null 2>&1 + + # ------------------------------------------------------------------------- + # 3. invoice_compliance_mx — asignar proveedores rotando entre los disponibles + # ------------------------------------------------------------------------- + exec_pg_sql "INSERT INTO a76.invoice_compliance_mx + (tenant_id, company_id, invoice_id, provider_id, created_at, updated_at) + SELECT + ih.tenant_id, + ih.company_id, + ih.id, + cp.id, + ih.created_at, + ih.updated_at + FROM a76.invoice_header ih + JOIN LATERAL ( + SELECT id FROM a76.clients_and_providers + WHERE tenant_id = ${TENANT_ID} + AND company_id = ${COMPANY_ID} + AND client_or_provider IN ('PROVIDER', 'BOTH') + ORDER BY id + OFFSET (ROW_NUMBER() OVER (ORDER BY ih.id) - 1) % ( + SELECT COUNT(*) FROM a76.clients_and_providers + WHERE tenant_id = ${TENANT_ID} AND company_id = ${COMPANY_ID} + AND client_or_provider IN ('PROVIDER', 'BOTH') + ) + LIMIT 1 + ) cp ON true + WHERE ih.tenant_id = ${TENANT_ID} + AND ih.company_id = ${COMPANY_ID} + AND ih.invoice_number LIKE 'SEED-%' + ON CONFLICT DO NOTHING;" >/dev/null 2>&1 + + # ------------------------------------------------------------------------- + # 4. invoice_logistics — rotar incoterms y transport_type + # ------------------------------------------------------------------------- + exec_pg_sql "INSERT INTO a76.invoice_logistics + (tenant_id, company_id, invoice_id, transport_type, weight_type, incoterm, created_at, updated_at) + SELECT + ih.tenant_id, + ih.company_id, + ih.id, + 'none', + 'kgs', + (ARRAY['FOB','CIF','EXW','DDP','DAP'])[((ROW_NUMBER() OVER (ORDER BY ih.id) - 1) % 5) + 1], + ih.created_at, + ih.updated_at + FROM a76.invoice_header ih + WHERE ih.tenant_id = ${TENANT_ID} + AND ih.company_id = ${COMPANY_ID} + AND ih.invoice_number LIKE 'SEED-%' + ON CONFLICT DO NOTHING;" >/dev/null 2>&1 +} + +# Ejecutar todas las funciones de seed +execute_seed_data() { + echo -e "\n${YELLOW}[SEED] Cargando datos de ejemplo...${NC}" + + seed_customs_brokers + seed_clients_and_providers + seed_packages + seed_classes + seed_parts + seed_pedimentos + seed_invoices + + echo -e "${GREEN}✓ Datos de ejemplo cargados exitosamente${NC}" + echo -e "${YELLOW} • 3 Agentes aduanales${NC}" + echo -e "${YELLOW} • 4 Clientes/Proveedores${NC}" + echo -e "${YELLOW} • 4 Tipos de paquete${NC}" + echo -e "${YELLOW} • 4 Clases${NC}" + echo -e "${YELLOW} • 5 Parts/Componentes${NC}" + echo -e "${YELLOW} • 3 Pedimentos${NC}" + echo -e "${YELLOW} • 56 Facturas (12 meses: Abr 2025 → Mar 2026)${NC}" +} + +# Variables de configuración KEYCLOAK_URL="${KEYCLOAK_URL:-http://localhost:8080/kcauth}" KEYCLOAK_ADMIN="${KEYCLOAK_ADMIN:-admin}" KEYCLOAK_ADMIN_PASSWORD="${KEYCLOAK_ADMIN_PASSWORD:-admin}" @@ -145,7 +422,7 @@ DEMO_EMAIL="demo@aduanasoft.com" DEMO_FIRSTNAME="Demo" DEMO_LASTNAME="User" -TENANT_NAME="Aduanasoft"A +TENANT_NAME="Aduanasoft" TENANT_SLUG="aduanasoft" COMPANY_NAME="Aduanasoft S.A. de C.V." COMPANY_RFC="ADS010101AAA" @@ -735,6 +1012,13 @@ else fi fi +############################################################################### +# 9. Cargar datos de ejemplo (opcional) +############################################################################### +if [ "$SEED_DATA" = true ]; then + execute_seed_data +fi + ############################################################################### # Resumen final ############################################################################### @@ -773,6 +1057,18 @@ echo -e " ${GREEN}✓${NC} Plan: Enterprise (ilimitado)" echo -e " ${GREEN}✓${NC} Status: Activa" echo -e " ${GREEN}✓${NC} Features: API, Reportes Avanzados, Integraciones, Soporte Dedicado" echo -e " ${GREEN}✓${NC} Vigencia: 1 año" + +if [ "$SEED_DATA" = true ]; then +echo -e "\n${YELLOW}Datos de ejemplo:${NC}" +echo -e " ${GREEN}✓${NC} Agentes aduanales: 3" +echo -e " ${GREEN}✓${NC} Clientes/Proveedores: 4" +echo -e " ${GREEN}✓${NC} Tipos de paquete: 4" +echo -e " ${GREEN}✓${NC} Clases: 4" +echo -e " ${GREEN}✓${NC} Parts/Componentes: 5" +echo -e " ${GREEN}✓${NC} Pedimentos: 3" +echo -e " ${GREEN}✓${NC} Facturas: 3" +fi + echo -e "\n${YELLOW}Puedes acceder al sistema en:${NC}" echo -e " ${GREEN}http://localhost:5173${NC}" echo -e "\n${GREEN}════════════════════════════════════════════════════════${NC}\n"