#!/bin/bash ############################################################################### # Script de inicialización completa para Anexo76 # # Este script configura automáticamente: # 1. User Profile Attributes en Keycloak (tenant_id) # 2. Clientes de Keycloak (Backend y Frontend) # 3. Mappers de atributos (tenant_id) # 4. Usuario demo en Keycloak (usuario=demo, password=demo123) # 5. Tenant y Company en PostgreSQL # 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 # - PostgreSQL corriendo en localhost:5432 o 5939 # - Base de datos anexo76_core creada # - jq instalado (para procesamiento JSON) # # Nota: El atributo tenant_id se crea con valor inicial "1" y luego # se actualiza con el ID real del tenant creado en PostgreSQL. ############################################################################### 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' YELLOW='\033[1;33m' NC='\033[0m' # No Color ############################################################################### # Validar dependencias requeridas ############################################################################### check_dependencies() { local missing_deps=() command -v jq >/dev/null 2>&1 || missing_deps+=("jq") command -v curl >/dev/null 2>&1 || missing_deps+=("curl") command -v docker >/dev/null 2>&1 || missing_deps+=("docker") if [ ${#missing_deps[@]} -ne 0 ]; then echo -e "${RED}✗ Error: Dependencias faltantes: ${missing_deps[*]}${NC}" >&2 echo -e "${YELLOW}Instala las dependencias faltantes antes de continuar.${NC}" >&2 exit 1 fi } check_dependencies ############################################################################### # Funciones auxiliares ############################################################################### # Ejecutar SQL en PostgreSQL via Docker exec_pg_sql() { local sql="$1" docker exec -e PGPASSWORD="${POSTGRES_PASSWORD}" anexo76-postgres-a76 \ psql -h localhost -p 5432 -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" \ -t -c "${sql}" 2>&1 } # Ejecutar SQL en el PostgreSQL del Cliente (Simulación) exec_pg_sql_client() { local sql="$1" # Solo ejecutar si el contenedor existe y está corriendo if docker ps --format '{{.Names}}' | grep -q "^anexo76-postgres-client$"; then docker exec -e PGPASSWORD="${POSTGRES_PASSWORD}" anexo76-postgres-client \ psql -h localhost -p 5432 -U "${POSTGRES_USER}" -d "anexo76_client" \ -t -c "${sql}" 2>/dev/null || true fi } # Crear mapper de tenant_id para un cliente create_tenant_mapper() { local client_id="$1" local client_name="$2" echo "Configurando mapper para ${client_name}..." # Verificar si el mapper ya existe local mappers mappers=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients/${client_id}/protocol-mappers/models" \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \ -H "Content-Type: application/json") if echo "$mappers" | jq -e '.[] | select(.name == "tenant-id-mapper")' >/dev/null 2>&1; then echo -e "${YELLOW}⚠ Mapper tenant_id ya existe para ${client_name}${NC}" return 0 fi # Crear mapper local response response=$(curl -s -w "\n%{http_code}" -X POST "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients/${client_id}/protocol-mappers/models" \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \ -H "Content-Type: application/json" \ -d '{ "name": "tenant-id-mapper", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", "config": { "user.attribute": "tenant_id", "claim.name": "tenant_id", "jsonType.label": "String", "id.token.claim": "true", "access.token.claim": "true", "userinfo.token.claim": "true" } }') local http_code http_code=$(echo "$response" | tail -n1) if [ "$http_code" = "201" ]; then echo -e "${GREEN}✓ Mapper tenant_id creado para ${client_name}${NC}" else echo -e "${YELLOW}⚠ No se pudo crear mapper para ${client_name} (HTTP ${http_code})${NC}" fi } ############################################################################### # 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_processed, 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}" KEYCLOAK_REALM="${KEYCLOAK_REALM:-master}" KEYCLOAK_ADMIN_URL="${KEYCLOAK_ADMIN_URL:-http://localhost:9000/kcauth}" POSTGRES_HOST="${POSTGRES_HOST:-localhost}" POSTGRES_PORT="${POSTGRES_PORT:-5432}" POSTGRES_DB="${POSTGRES_DB:-anexo76_core}" POSTGRES_USER="${POSTGRES_USER:-postgres}" POSTGRES_PASSWORD="${POSTGRES_PASSWORD:-postgres}" BACKEND_URL="${BACKEND_URL:-http://localhost:8000}" DEMO_USERNAME="demo" DEMO_PASSWORD="demo123" DEMO_EMAIL="demo@aduanasoft.com" DEMO_FIRSTNAME="Demo" DEMO_LASTNAME="User" TENANT_NAME="Aduanasoft" TENANT_SLUG="aduanasoft" COMPANY_NAME="Aduanasoft S.A. de C.V." COMPANY_RFC="ADS010101AAA" echo -e "${GREEN}════════════════════════════════════════════════════════${NC}" echo -e "${GREEN} Inicialización del Sistema Anexo76${NC}" echo -e "${GREEN}════════════════════════════════════════════════════════${NC}" ############################################################################### # 1. Esperar a que Keycloak esté listo ############################################################################### echo -e "\n${YELLOW}[1/8] Esperando a que Keycloak esté disponible...${NC}" MAX_RETRIES=30 RETRY_COUNT=0 while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do if curl -s -f "${KEYCLOAK_ADMIN_URL}/health/ready" > /dev/null 2>&1; then echo -e "${GREEN}✓ Keycloak está listo${NC}" break fi RETRY_COUNT=$((RETRY_COUNT + 1)) if [ $((RETRY_COUNT % 5)) -eq 0 ]; then echo "Esperando Keycloak... ($RETRY_COUNT/$MAX_RETRIES)" fi sleep 2 done if [ $RETRY_COUNT -eq $MAX_RETRIES ]; then echo -e "${RED}✗ Error: Keycloak no está disponible después de $MAX_RETRIES intentos${NC}" exit 1 fi ############################################################################### # 2. Obtener token de administrador de Keycloak ############################################################################### echo -e "\n${YELLOW}[2/8] Obteniendo token de administrador de Keycloak...${NC}" TOKEN_RESPONSE=$(curl -s -X POST "${KEYCLOAK_URL}/realms/master/protocol/openid-connect/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "username=${KEYCLOAK_ADMIN}" \ -d "password=${KEYCLOAK_ADMIN_PASSWORD}" \ -d "grant_type=password" \ -d "client_id=admin-cli") ACCESS_TOKEN=$(echo "$TOKEN_RESPONSE" | jq -r '.access_token // empty') if [ -z "$ACCESS_TOKEN" ]; then echo -e "${RED}✗ Error: No se pudo obtener el token de acceso${NC}" echo "Respuesta: $TOKEN_RESPONSE" exit 1 fi echo -e "${GREEN}✓ Token obtenido exitosamente${NC}" ############################################################################### # 3. Configurar User Profile Attributes con valores por defecto ############################################################################### echo -e "\n${YELLOW}[3/8] Configurando User Profile Attributes...${NC}" # Obtener configuración actual del User Profile USER_PROFILE=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/users/profile" \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \ -H "Content-Type: application/json") # Verificar si tenant_id existe usando jq TENANT_ID_EXISTS=$(echo "$USER_PROFILE" | jq -e '.attributes[]? | select(.name == "tenant_id")' >/dev/null 2>&1 && echo "true" || echo "false") if [ "$TENANT_ID_EXISTS" = "false" ]; then echo "Agregando atributo tenant_id al User Profile..." # Crear el atributo tenant_id con valor por defecto UPDATED_PROFILE=$(echo "$USER_PROFILE" | jq '.attributes += [{ "name": "tenant_id", "displayName": "Tenant ID", "validations": {}, "annotations": { "inputType": "text" }, "required": { "roles": [], "scopes": [] }, "permissions": { "view": ["admin", "user"], "edit": ["admin"] }, "multivalued": false, "group": null }]') # Actualizar el User Profile curl -s -X PUT "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/users/profile" \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \ -H "Content-Type: application/json" \ -d "$UPDATED_PROFILE" echo -e "${GREEN}✓ Atributo tenant_id agregado al User Profile${NC}" else echo -e "${YELLOW}⚠ Atributo tenant_id ya existe en User Profile${NC}" fi ############################################################################### # 4. Configurar clientes de Keycloak (Backend y Frontend) ############################################################################### echo -e "\n${YELLOW}[4/8] Configurando clientes de Keycloak...${NC}" # 3.1 Crear cliente Backend echo "Configurando cliente Backend..." BACKEND_CLIENT_EXISTS=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients?clientId=anexo76-backend" \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \ -H "Content-Type: application/json") if echo "$BACKEND_CLIENT_EXISTS" | jq -e '.[] | select(.clientId == "anexo76-backend")' >/dev/null 2>&1; then echo -e "${YELLOW}⚠ Cliente Backend ya existe${NC}" BACKEND_CLIENT_ID=$(echo "$BACKEND_CLIENT_EXISTS" | jq -r '.[0].id') else CREATE_BACKEND=$(curl -s -w "\n%{http_code}" -X POST "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients" \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \ -H "Content-Type: application/json" \ -d '{ "clientId": "anexo76-backend", "name": "Anexo76 Backend API", "description": "Backend API para el sistema Anexo76", "enabled": true, "protocol": "openid-connect", "publicClient": false, "serviceAccountsEnabled": true, "directAccessGrantsEnabled": true, "standardFlowEnabled": true, "implicitFlowEnabled": false, "rootUrl": "http://localhost:8000/api", "baseUrl": "http://localhost:8000/api", "redirectUris": [ "http://localhost:8000/*", "http://localhost:5173/*" ], "webOrigins": ["http://localhost:8000/api"], "attributes": { "access.token.lifespan": "3600", "client.secret.creation.time": "0" } }') HTTP_CODE=$(echo "$CREATE_BACKEND" | tail -n1) if [ "$HTTP_CODE" = "201" ]; then echo -e "${GREEN}✓ Cliente Backend creado${NC}" # Obtener ID del cliente recién creado BACKEND_CLIENT_EXISTS=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients?clientId=anexo76-backend" \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \ -H "Content-Type: application/json") BACKEND_CLIENT_ID=$(echo "$BACKEND_CLIENT_EXISTS" | jq -r '.[0].id') else echo -e "${RED}✗ Error al crear cliente Backend (HTTP ${HTTP_CODE})${NC}" fi fi # Obtener y mostrar el secret del cliente Backend if [ -n "$BACKEND_CLIENT_ID" ]; then BACKEND_SECRET=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients/${BACKEND_CLIENT_ID}/client-secret" \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \ -H "Content-Type: application/json" | jq -r '.value // empty') echo -e "${GREEN}✓ Backend Client ID: anexo76-backend${NC}" echo -e "${GREEN}✓ Backend Client Secret: ${BACKEND_SECRET}${NC}" # Actualizar archivo .env con el client secret if [ -n "$BACKEND_SECRET" ]; then ENV_FILE="" # Buscar archivo .env en el directorio actual o en el directorio padre if [ -f ".env" ]; then ENV_FILE=".env" elif [ -f "../.env" ]; then ENV_FILE="../.env" fi if [ -n "$ENV_FILE" ]; then # Verificar si la variable ya existe en el archivo if grep -q "^KEYCLOAK_CLIENT_SECRET=" "$ENV_FILE"; then # Actualizar el valor existente if [[ "$OSTYPE" == "darwin"* ]]; then # macOS sed -i '' "s|^KEYCLOAK_CLIENT_SECRET=.*|KEYCLOAK_CLIENT_SECRET=${BACKEND_SECRET}|" "$ENV_FILE" else # Linux sed -i "s|^KEYCLOAK_CLIENT_SECRET=.*|KEYCLOAK_CLIENT_SECRET=${BACKEND_SECRET}|" "$ENV_FILE" fi echo -e "${GREEN}✓ KEYCLOAK_CLIENT_SECRET actualizado en ${ENV_FILE}${NC}" else # Agregar la variable si no existe echo "KEYCLOAK_CLIENT_SECRET=${BACKEND_SECRET}" >> "$ENV_FILE" echo -e "${GREEN}✓ KEYCLOAK_CLIENT_SECRET agregado a ${ENV_FILE}${NC}" fi else echo -e "${YELLOW}⚠ No se encontró archivo .env, el secret debe agregarse manualmente${NC}" fi fi fi # 3.2 Crear cliente Frontend echo "Configurando cliente Frontend..." FRONTEND_CLIENT_EXISTS=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients?clientId=anexo76-frontend" \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \ -H "Content-Type: application/json") if echo "$FRONTEND_CLIENT_EXISTS" | jq -e '.[] | select(.clientId == "anexo76-frontend")' >/dev/null 2>&1; then echo -e "${YELLOW}⚠ Cliente Frontend ya existe, actualizando configuración...${NC}" FRONTEND_CLIENT_ID=$(echo "$FRONTEND_CLIENT_EXISTS" | jq -r '.[0].id') # Actualizar configuración del cliente existente para incluir puertos nuevos UPDATE_FRONTEND=$(curl -s -w "\n%{http_code}" -X PUT "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients/${FRONTEND_CLIENT_ID}" \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \ -H "Content-Type: application/json" \ -d '{ "clientId": "anexo76-frontend", "name": "Anexo76 Frontend", "description": "Aplicación web frontend para el sistema Anexo76", "enabled": true, "protocol": "openid-connect", "publicClient": true, "directAccessGrantsEnabled": false, "standardFlowEnabled": true, "implicitFlowEnabled": false, "rootUrl": "http://localhost:5173", "baseUrl": "http://localhost:5173", "redirectUris": [ "http://localhost:5173/*", "http://localhost:5174/*", "http://localhost:3000/*" ], "webOrigins": [ "http://localhost:5173", "http://localhost:5174", "http://localhost:3000" ], "attributes": { "pkce.code.challenge.method": "S256" } }') HTTP_CODE=$(echo "$UPDATE_FRONTEND" | tail -n1) if [ "$HTTP_CODE" = "204" ] || [ "$HTTP_CODE" = "200" ]; then echo -e "${GREEN}✓ Cliente Frontend actualizado (Puertos actualizados)${NC}" else echo -e "${RED}✗ Error al actualizar cliente Frontend (HTTP ${HTTP_CODE})${NC}" fi else CREATE_FRONTEND=$(curl -s -w "\n%{http_code}" -X POST "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients" \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \ -H "Content-Type: application/json" \ -d '{ "clientId": "anexo76-frontend", "name": "Anexo76 Frontend", "description": "Aplicación web frontend para el sistema Anexo76", "enabled": true, "protocol": "openid-connect", "publicClient": true, "directAccessGrantsEnabled": false, "standardFlowEnabled": true, "implicitFlowEnabled": false, "rootUrl": "http://localhost:5173", "baseUrl": "http://localhost:5173", "redirectUris": [ "http://localhost:5173/*", "http://localhost:5174/*", "http://localhost:3000/*" ], "webOrigins": [ "http://localhost:5173", "http://localhost:5174", "http://localhost:3000" ], "attributes": { "pkce.code.challenge.method": "S256" } }') HTTP_CODE=$(echo "$CREATE_FRONTEND" | tail -n1) RESPONSE_BODY=$(echo "$CREATE_FRONTEND" | head -n -1) if [ "$HTTP_CODE" = "201" ]; then echo -e "${GREEN}✓ Cliente Frontend creado${NC}" # Obtener ID del cliente recién creado FRONTEND_CLIENT_EXISTS=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients?clientId=anexo76-frontend" \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \ -H "Content-Type: application/json") FRONTEND_CLIENT_ID=$(echo "$FRONTEND_CLIENT_EXISTS" | jq -r '.[0].id') else echo -e "${RED}✗ Error al crear cliente Frontend (HTTP ${HTTP_CODE})${NC}" echo "Respuesta del servidor: $RESPONSE_BODY" fi fi ############################################################################### # 5. Configurar Mappers para tenant_id ############################################################################### echo -e "\n${YELLOW}[5/8] Configurando mappers para tenant_id...${NC}" # 4.1 Configurar mapper para Backend if [ -n "$BACKEND_CLIENT_ID" ]; then create_tenant_mapper "$BACKEND_CLIENT_ID" "Backend" fi # 4.2 Configurar mapper para Frontend if [ -n "$FRONTEND_CLIENT_ID" ]; then create_tenant_mapper "$FRONTEND_CLIENT_ID" "Frontend" fi ############################################################################### # 6. Crear usuario demo en Keycloak ############################################################################### echo -e "\n${YELLOW}[6/8] Creando usuario demo en Keycloak...${NC}" # Verificar si el usuario ya existe USER_EXISTS=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/users?username=${DEMO_USERNAME}" \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \ -H "Content-Type: application/json") if echo "$USER_EXISTS" | jq -e '.[] | select(.username == "'"${DEMO_USERNAME}"'")' >/dev/null 2>&1; then echo -e "${YELLOW}⚠ Usuario demo ya existe, actualizando...${NC}" USER_ID=$(echo "$USER_EXISTS" | jq -r '.[0].id') # Actualizar usuario existente curl -s -X PUT "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/users/${USER_ID}" \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \ -H "Content-Type: application/json" \ -d "{ \"enabled\": true, \"firstName\": \"${DEMO_FIRSTNAME}\", \"lastName\": \"${DEMO_LASTNAME}\", \"email\": \"${DEMO_EMAIL}\", \"emailVerified\": true }" # Resetear contraseña curl -s -X PUT "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/users/${USER_ID}/reset-password" \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \ -H "Content-Type: application/json" \ -d "{ \"type\": \"password\", \"value\": \"${DEMO_PASSWORD}\", \"temporary\": false }" echo -e "${GREEN}✓ Usuario demo actualizado${NC}" else # Crear nuevo usuario con tenant_id por defecto CREATE_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/users" \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \ -H "Content-Type: application/json" \ -d "{ \"username\": \"${DEMO_USERNAME}\", \"enabled\": true, \"firstName\": \"${DEMO_FIRSTNAME}\", \"lastName\": \"${DEMO_LASTNAME}\", \"email\": \"${DEMO_EMAIL}\", \"emailVerified\": true, \"attributes\": { \"tenant_id\": [\"1\"] }, \"credentials\": [{ \"type\": \"password\", \"value\": \"${DEMO_PASSWORD}\", \"temporary\": false }] }") HTTP_CODE=$(echo "$CREATE_RESPONSE" | tail -n1) if [ "$HTTP_CODE" = "201" ]; then echo -e "${GREEN}✓ Usuario demo creado exitosamente${NC}" # Obtener el ID del usuario recién creado USER_RESPONSE=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/users?username=${DEMO_USERNAME}" \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \ -H "Content-Type: application/json") USER_ID=$(echo "$USER_RESPONSE" | jq -r '.[0].id') else echo -e "${RED}✗ Error al crear usuario (HTTP ${HTTP_CODE})${NC}" echo "Respuesta: $CREATE_RESPONSE" exit 1 fi fi echo -e "${GREEN}✓ Usuario: ${DEMO_USERNAME}${NC}" echo -e "${GREEN}✓ Password: ${DEMO_PASSWORD}${NC}" echo -e "${GREEN}✓ Keycloak User ID: ${USER_ID}${NC}" ############################################################################### # 7. Agregar atributo tenant_id al usuario ############################################################################### echo -e "\n${YELLOW}[7/9] Configurando atributo tenant_id para usuario demo...${NC}" # Nota: El tenant_id se agregará después de crear el tenant en PostgreSQL ############################################################################### # 8. Crear tenant y company en PostgreSQL ############################################################################### echo -e "\n${YELLOW}[8/9] Creando tenant y company en PostgreSQL...${NC}" # Esperar a que PostgreSQL esté listo echo "Esperando a que PostgreSQL esté disponible..." MAX_PG_RETRIES=60 PG_RETRY_COUNT=0 while [ $PG_RETRY_COUNT -lt $MAX_PG_RETRIES ]; do # Usar docker exec para verificar pg_isready directamente if docker exec anexo76-postgres-a76 pg_isready -h localhost -U "${POSTGRES_USER}" > /dev/null 2>&1; then echo -e "${GREEN}✓ PostgreSQL está listo${NC}" break fi PG_RETRY_COUNT=$((PG_RETRY_COUNT + 1)) if [ $((PG_RETRY_COUNT % 10)) -eq 0 ]; then echo "Esperando PostgreSQL... ($PG_RETRY_COUNT/$MAX_PG_RETRIES)" fi sleep 1 done if [ $PG_RETRY_COUNT -eq $MAX_PG_RETRIES ]; then echo -e "${RED}✗ Error: PostgreSQL no está disponible después de $MAX_PG_RETRIES intentos${NC}" exit 1 fi # Insertar o actualizar tenant echo "Insertando tenant en PostgreSQL (Hub y Cliente)..." exec_pg_sql "INSERT INTO core.tenants (name, slug, type, keycloak_realm, contact_email, is_active, created_at, updated_at) VALUES ('${TENANT_NAME}', '${TENANT_SLUG}', 'SHARED'::tenanttype, '${KEYCLOAK_REALM}', '${DEMO_EMAIL}', true, now(), now()) ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, contact_email = EXCLUDED.contact_email, updated_at = CURRENT_TIMESTAMP;" >/dev/null exec_pg_sql_client "INSERT INTO core.tenants (name, slug, type, keycloak_realm, contact_email, is_active, created_at, updated_at) VALUES ('${TENANT_NAME}', '${TENANT_SLUG}', 'SHARED'::tenanttype, '${KEYCLOAK_REALM}', '${DEMO_EMAIL}', true, now(), now()) ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, contact_email = EXCLUDED.contact_email, updated_at = CURRENT_TIMESTAMP;" >/dev/null # Obtener el ID del tenant con mejor manejo de errores echo "Obteniendo ID del tenant..." set +e TENANT_ID_RESULT=$(exec_pg_sql "SELECT id FROM core.tenants WHERE slug = '${TENANT_SLUG}';") TENANT_QUERY_STATUS=$? set -e if [ $TENANT_QUERY_STATUS -ne 0 ]; then echo -e "${RED}✗ Error al consultar el tenant:${NC}" echo "$TENANT_ID_RESULT" echo -e "${YELLOW}Verificando si la tabla existe...${NC}" exec_pg_sql "\dt core.tenants;" exit 1 fi TENANT_ID=$(echo "$TENANT_ID_RESULT" | xargs) # Trim whitespace if [ -z "$TENANT_ID" ]; then echo -e "${RED}✗ Error: No se pudo obtener el ID del tenant${NC}" echo "Resultado de la consulta: '$TENANT_ID_RESULT'" echo -e "${YELLOW}Intentando ver todos los tenants...${NC}" exec_pg_sql "SELECT id, slug FROM core.tenants LIMIT 10;" exit 1 fi echo -e "${GREEN}✓ Tenant ID: ${TENANT_ID}${NC}" # Agregar tenant_id al usuario demo en Keycloak echo -e "\n${YELLOW}Asignando tenant_id al usuario demo...${NC}" curl -s -X PUT "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/users/${USER_ID}" \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \ -H "Content-Type: application/json" \ -d "{ \"attributes\": { \"tenant_id\": [\"${TENANT_ID}\"] } }" echo -e "${GREEN}✓ Atributo tenant_id asignado al usuario${NC}" # Obtener token de demo user para usar la API con su tenant echo "Obteniendo token de usuario demo..." DEMO_TOKEN_RESPONSE=$(curl -s -X POST "${KEYCLOAK_URL}/realms/${KEYCLOAK_REALM}/protocol/openid-connect/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "username=${DEMO_USERNAME}" \ -d "password=${DEMO_PASSWORD}" \ -d "grant_type=password" \ -d "client_id=anexo76-backend" \ -d "client_secret=${BACKEND_SECRET}") DEMO_ACCESS_TOKEN=$(echo "$DEMO_TOKEN_RESPONSE" | jq -r '.access_token // empty') if [ -z "$DEMO_ACCESS_TOKEN" ]; then echo -e "${RED}✗ Error: No se pudo obtener el token de usuario demo${NC}" exit 1 fi # Insertar company si no existe COMPANY_EXISTS=$(exec_pg_sql "SELECT COUNT(*) FROM a76.company WHERE tenant_id = ${TENANT_ID};") COMPANY_EXISTS=$(echo "$COMPANY_EXISTS" | xargs) if [ "$COMPANY_EXISTS" = "0" ]; then echo " → Creando company mediante API..." CREATE_COMPANY_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "${BACKEND_URL}/api/v1/a76/company" \ -H "Authorization: Bearer ${DEMO_ACCESS_TOKEN}" \ -H "Content-Type: application/json" \ -d "{ \"name\": \"${COMPANY_NAME}\", \"rfc\": \"${COMPANY_RFC}\", \"is_service_company\": false }") HTTP_CODE=$(echo "$CREATE_COMPANY_RESPONSE" | tail -n1) RESPONSE_BODY=$(echo "$CREATE_COMPANY_RESPONSE" | head -n -1) if [ "$HTTP_CODE" = "201" ]; then COMPANY_ID=$(echo "$RESPONSE_BODY" | jq -r '.id // empty') echo -e "${GREEN}✓ Company creada exitosamente via API (Hub, Cliente replicado por DB trigger o servicio asíncrono)${NC}" else echo -e "${RED}✗ Error al crear company via API (HTTP ${HTTP_CODE})${NC}" echo "Respuesta: $RESPONSE_BODY" exit 1 fi else echo -e "${YELLOW}⚠ Company ya existe para este tenant${NC}" # Obtener información de la company existente COMPANY_ID=$(exec_pg_sql "SELECT id FROM a76.company WHERE tenant_id = ${TENANT_ID} LIMIT 1;" | xargs) fi COMPANY_INFO=$(exec_pg_sql "SELECT name FROM a76.company WHERE id = ${COMPANY_ID} AND tenant_id = ${TENANT_ID} LIMIT 1;") echo -e "${GREEN}✓ Company: ${COMPANY_INFO}${NC}" echo -e "${GREEN}✓ Company ID: ${COMPANY_ID}${NC}" # Agregar relación usuario-tenant en la base de datos echo -e "\n${YELLOW}Creando relación usuario-tenant en la base de datos...${NC}" exec_pg_sql "INSERT INTO core.user_tenants (keycloak_user_id, tenant_id, company_id, role, is_active, created_at, updated_at) VALUES ('${USER_ID}', ${TENANT_ID}, ${COMPANY_ID}, 'admin', true, now(), now()) ON CONFLICT (keycloak_user_id, tenant_id, company_id) DO UPDATE SET is_active = true, updated_at = CURRENT_TIMESTAMP;" >/dev/null exec_pg_sql_client "INSERT INTO core.user_tenants (keycloak_user_id, tenant_id, company_id, role, is_active, created_at, updated_at) VALUES ('${USER_ID}', ${TENANT_ID}, ${COMPANY_ID}, 'admin', true, now(), now()) ON CONFLICT (keycloak_user_id, tenant_id, company_id) DO UPDATE SET is_active = true, updated_at = CURRENT_TIMESTAMP;" >/dev/null echo -e "${GREEN}✓ Relación usuario-tenant creada en la base de datos (Hub y Cliente)${NC}" ############################################################################### # 9. Crear licencia Enterprise para el tenant ############################################################################### echo -e "\n${YELLOW}[9/9] Creando licencia Enterprise para el tenant...${NC}" # Calcular fechas de inicio y expiración (1 año desde hoy) LICENSE_START_DATE=$(date -u +"%Y-%m-%d %H:%M:%S") LICENSE_EXPIRE_DATE=$(date -u -d "+1 year" +"%Y-%m-%d %H:%M:%S") # Verificar si ya existe una licencia para este tenant LICENSE_EXISTS=$(exec_pg_sql "SELECT COUNT(*) FROM core.licenses WHERE tenant_id = ${TENANT_ID};") LICENSE_EXISTS=$(echo "$LICENSE_EXISTS" | xargs) if [ "$LICENSE_EXISTS" = "0" ]; then # Crear licencia Enterprise con límites ilimitados (valores muy altos) set +e # Desactivar exit on error temporalmente LICENSE_CREATE_OUTPUT=$(exec_pg_sql "INSERT INTO core.licenses (tenant_id, plan, status, max_users, max_storage_gb, max_monthly_operations, feature_api_access, feature_advanced_reports, feature_integrations, feature_dedicated_support, starts_at, expires_at, created_at, updated_at) VALUES (${TENANT_ID}, 'ENTERPRISE', 'ACTIVE', 999999, 999999, 999999, true, true, true, true, '${LICENSE_START_DATE}'::timestamp, '${LICENSE_EXPIRE_DATE}'::timestamp, now(), now());" 2>&1) LICENSE_CREATE_STATUS=$? set -e # Reactivar exit on error if [ $LICENSE_CREATE_STATUS -eq 0 ]; then exec_pg_sql_client "INSERT INTO core.licenses (tenant_id, plan, status, max_users, max_storage_gb, max_monthly_operations, feature_api_access, feature_advanced_reports, feature_integrations, feature_dedicated_support, starts_at, expires_at, created_at, updated_at) VALUES (${TENANT_ID}, 'ENTERPRISE', 'ACTIVE', 999999, 999999, 999999, true, true, true, true, '${LICENSE_START_DATE}'::timestamp, '${LICENSE_EXPIRE_DATE}'::timestamp, now(), now());" >/dev/null 2>&1 || true echo -e "${GREEN}✓ Licencia Enterprise creada exitosamente (Hub y Cliente)${NC}" echo -e "${GREEN} Plan: Enterprise${NC}" echo -e "${GREEN} Usuarios: Ilimitados${NC}" echo -e "${GREEN} Almacenamiento: Ilimitado${NC}" echo -e "${GREEN} Operaciones mensuales: Ilimitadas${NC}" echo -e "${GREEN} Inicio: ${LICENSE_START_DATE}${NC}" echo -e "${GREEN} Expiración: ${LICENSE_EXPIRE_DATE}${NC}" echo -e "${GREEN} Features: API, Reportes Avanzados, Integraciones, Soporte Dedicado${NC}" else echo -e "${RED}✗ Error al crear la licencia${NC}" echo -e "${YELLOW}Detalle del error:${NC}" echo "$LICENSE_CREATE_OUTPUT" exit 1 fi else echo -e "${YELLOW}⚠ Ya existe una licencia para este tenant, actualizando a Enterprise...${NC}" # Actualizar licencia existente a Enterprise set +e # Desactivar exit on error temporalmente exec_pg_sql "UPDATE core.licenses SET plan = 'ENTERPRISE', status = 'ACTIVE', max_users = 999999, max_storage_gb = 999999, max_monthly_operations = 999999, feature_api_access = true, feature_advanced_reports = true, feature_integrations = true, feature_dedicated_support = true, starts_at = '${LICENSE_START_DATE}'::timestamp, expires_at = '${LICENSE_EXPIRE_DATE}'::timestamp, updated_at = now() WHERE tenant_id = ${TENANT_ID};" >/dev/null 2>&1 exec_pg_sql_client "UPDATE core.licenses SET plan = 'ENTERPRISE', status = 'ACTIVE', max_users = 999999, max_storage_gb = 999999, max_monthly_operations = 999999, feature_api_access = true, feature_advanced_reports = true, feature_integrations = true, feature_dedicated_support = true, starts_at = '${LICENSE_START_DATE}'::timestamp, expires_at = '${LICENSE_EXPIRE_DATE}'::timestamp, updated_at = now() WHERE tenant_id = ${TENANT_ID};" >/dev/null 2>&1 || true LICENSE_UPDATE_STATUS=$? set -e # Reactivar exit on error if [ $LICENSE_UPDATE_STATUS -eq 0 ]; then echo -e "${GREEN}✓ Licencia actualizada a Enterprise (Hub y Cliente)${NC}" else echo -e "${RED}✗ Error al actualizar la licencia${NC}" exit 1 fi fi ############################################################################### # 9. Cargar datos de ejemplo (opcional) ############################################################################### if [ "$SEED_DATA" = true ]; then execute_seed_data fi ############################################################################### # Resumen final ############################################################################### echo -e "\n${GREEN}════════════════════════════════════════════════════════${NC}" echo -e "${GREEN} ✓ Inicialización completada exitosamente${NC}" echo -e "${GREEN}════════════════════════════════════════════════════════${NC}" echo -e "\n${YELLOW}Credenciales de acceso:${NC}" echo -e " Usuario: ${GREEN}${DEMO_USERNAME}${NC}" echo -e " Password: ${GREEN}${DEMO_PASSWORD}${NC}" echo -e " Email: ${GREEN}${DEMO_EMAIL}${NC}" echo -e "\n${YELLOW}Información del sistema:${NC}" echo -e " Tenant: ${GREEN}${TENANT_NAME} (ID: ${TENANT_ID})${NC}" echo -e " Realm: ${GREEN}${KEYCLOAK_REALM}${NC}" echo -e " Keycloak URL: ${GREEN}${KEYCLOAK_URL}${NC}" echo -e "\n${YELLOW}Clientes Keycloak configurados:${NC}" echo -e " Backend Client ID: ${GREEN}anexo76-backend${NC}" if [ -n "$BACKEND_SECRET" ]; then echo -e " Backend Secret: ${GREEN}${BACKEND_SECRET}${NC}" if [ -n "$ENV_FILE" ]; then echo -e " ${GREEN}✓${NC} KEYCLOAK_CLIENT_SECRET actualizado en ${ENV_FILE}" else echo -e " ${YELLOW}⚠${NC} Agregar manualmente al archivo .env como KEYCLOAK_CLIENT_SECRET" fi fi echo -e " Frontend Client ID: ${GREEN}anexo76-frontend${NC}" echo -e "\n${YELLOW}User Profile Attributes configurados:${NC}" echo -e " ${GREEN}✓${NC} tenant_id (opcional, valor inicial: 1)" echo -e "\n${YELLOW}Mappers configurados:${NC}" echo -e " ${GREEN}✓${NC} tenant_id mapper para Backend" echo -e " ${GREEN}✓${NC} tenant_id mapper para Frontend" echo -e "\n${YELLOW}Configuración del usuario demo:${NC}" echo -e " ${GREEN}✓${NC} tenant_id actualizado al ID real: ${TENANT_ID}" echo -e " ${GREEN}✓${NC} Relación usuario-tenant creada (rol: admin)" echo -e "\n${YELLOW}Licencia Enterprise:${NC}" 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"