Correccion de Login SIU en iOS

This commit is contained in:
Carlos Facundo Tello
2026-08-28 17:22:24 -03:00
parent 29284ae131
commit f7018a5237
31 changed files with 897 additions and 176 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ android {
buildTypes { buildTypes {
release { release {
minifyEnabled false minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
} }
} }
} }
+1 -1
View File
@@ -1,7 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
+1
View File
@@ -20,3 +20,4 @@ org.gradle.jvmargs=-Xmx1536m
# Android operating system, and which are packaged with your app's APK # Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn # https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true android.useAndroidX=true
+1
View File
@@ -0,0 +1 @@
java.home=/Users/carlosfacundotello/Library/Java/JavaVirtualMachines/jbr-21.0.11/Contents/Home
Vendored Regular → Executable
View File
+13
View File
@@ -0,0 +1,13 @@
App/build
App/Pods
App/output
App/App/public
DerivedData
xcuserdata
# Cordova plugins for Capacitor
capacitor-cordova-ios-plugins
# Generated Config files
App/App/capacitor.config.json
App/App/config.xml
+376
View File
@@ -0,0 +1,376 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 60;
objects = {
/* Begin PBXBuildFile section */
2FAD9763203C412B000D30F8 /* config.xml in Resources */ = {isa = PBXBuildFile; fileRef = 2FAD9762203C412B000D30F8 /* config.xml */; };
4D22ABE92AF431CB00220026 /* CapApp-SPM in Frameworks */ = {isa = PBXBuildFile; productRef = 4D22ABE82AF431CB00220026 /* CapApp-SPM */; };
50379B232058CBB4000EE86E /* capacitor.config.json in Resources */ = {isa = PBXBuildFile; fileRef = 50379B222058CBB4000EE86E /* capacitor.config.json */; };
504EC3081FED79650016851F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504EC3071FED79650016851F /* AppDelegate.swift */; };
504EC30D1FED79650016851F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC30B1FED79650016851F /* Main.storyboard */; };
504EC30F1FED79650016851F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 504EC30E1FED79650016851F /* Assets.xcassets */; };
504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC3101FED79650016851F /* LaunchScreen.storyboard */; };
50B271D11FEDC1A000F3C39B /* public in Resources */ = {isa = PBXBuildFile; fileRef = 50B271D01FEDC1A000F3C39B /* public */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
2FAD9762203C412B000D30F8 /* config.xml */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = config.xml; sourceTree = "<group>"; };
50379B222058CBB4000EE86E /* capacitor.config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = capacitor.config.json; sourceTree = "<group>"; };
504EC3041FED79650016851F /* App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = App.app; sourceTree = BUILT_PRODUCTS_DIR; };
504EC3071FED79650016851F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
504EC30C1FED79650016851F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
504EC30E1FED79650016851F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
504EC3111FED79650016851F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
504EC3131FED79650016851F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
50B271D01FEDC1A000F3C39B /* public */ = {isa = PBXFileReference; lastKnownFileType = folder; path = public; sourceTree = "<group>"; };
958DCC722DB07C7200EA8C5F /* debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = debug.xcconfig; path = ../debug.xcconfig; sourceTree = SOURCE_ROOT; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
504EC3011FED79650016851F /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
4D22ABE92AF431CB00220026 /* CapApp-SPM in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
504EC2FB1FED79650016851F = {
isa = PBXGroup;
children = (
958DCC722DB07C7200EA8C5F /* debug.xcconfig */,
504EC3061FED79650016851F /* App */,
504EC3051FED79650016851F /* Products */,
);
sourceTree = "<group>";
};
504EC3051FED79650016851F /* Products */ = {
isa = PBXGroup;
children = (
504EC3041FED79650016851F /* App.app */,
);
name = Products;
sourceTree = "<group>";
};
504EC3061FED79650016851F /* App */ = {
isa = PBXGroup;
children = (
50379B222058CBB4000EE86E /* capacitor.config.json */,
504EC3071FED79650016851F /* AppDelegate.swift */,
504EC30B1FED79650016851F /* Main.storyboard */,
504EC30E1FED79650016851F /* Assets.xcassets */,
504EC3101FED79650016851F /* LaunchScreen.storyboard */,
504EC3131FED79650016851F /* Info.plist */,
2FAD9762203C412B000D30F8 /* config.xml */,
50B271D01FEDC1A000F3C39B /* public */,
);
path = App;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
504EC3031FED79650016851F /* App */ = {
isa = PBXNativeTarget;
buildConfigurationList = 504EC3161FED79650016851F /* Build configuration list for PBXNativeTarget "App" */;
buildPhases = (
504EC3001FED79650016851F /* Sources */,
504EC3011FED79650016851F /* Frameworks */,
504EC3021FED79650016851F /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = App;
packageProductDependencies = (
4D22ABE82AF431CB00220026 /* CapApp-SPM */,
);
productName = App;
productReference = 504EC3041FED79650016851F /* App.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
504EC2FC1FED79650016851F /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0920;
LastUpgradeCheck = 0920;
TargetAttributes = {
504EC3031FED79650016851F = {
CreatedOnToolsVersion = 9.2;
LastSwiftMigration = 1100;
ProvisioningStyle = Automatic;
};
};
};
buildConfigurationList = 504EC2FF1FED79650016851F /* Build configuration list for PBXProject "App" */;
compatibilityVersion = "Xcode 8.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 504EC2FB1FED79650016851F;
packageReferences = (
D4C12C0A2AAA248700AAC8A2 /* XCLocalSwiftPackageReference "CapApp-SPM" */,
);
productRefGroup = 504EC3051FED79650016851F /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
504EC3031FED79650016851F /* App */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
504EC3021FED79650016851F /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */,
50B271D11FEDC1A000F3C39B /* public in Resources */,
504EC30F1FED79650016851F /* Assets.xcassets in Resources */,
50379B232058CBB4000EE86E /* capacitor.config.json in Resources */,
504EC30D1FED79650016851F /* Main.storyboard in Resources */,
2FAD9763203C412B000D30F8 /* config.xml in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
504EC3001FED79650016851F /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
504EC3081FED79650016851F /* AppDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
504EC30B1FED79650016851F /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
504EC30C1FED79650016851F /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
504EC3101FED79650016851F /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
504EC3111FED79650016851F /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
504EC3141FED79650016851F /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 958DCC722DB07C7200EA8C5F /* debug.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
504EC3151FED79650016851F /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
504EC3171FED79650016851F /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 958DCC722DB07C7200EA8C5F /* debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
INFOPLIST_FILE = App/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
PRODUCT_BUNDLE_IDENTIFIER = ar.edu.unicaba.cartelera;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
504EC3181FED79650016851F /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
INFOPLIST_FILE = App/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = ar.edu.unicaba.cartelera;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
504EC2FF1FED79650016851F /* Build configuration list for PBXProject "App" */ = {
isa = XCConfigurationList;
buildConfigurations = (
504EC3141FED79650016851F /* Debug */,
504EC3151FED79650016851F /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
504EC3161FED79650016851F /* Build configuration list for PBXNativeTarget "App" */ = {
isa = XCConfigurationList;
buildConfigurations = (
504EC3171FED79650016851F /* Debug */,
504EC3181FED79650016851F /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCLocalSwiftPackageReference section */
D4C12C0A2AAA248700AAC8A2 /* XCLocalSwiftPackageReference "CapApp-SPM" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = "CapApp-SPM";
};
/* End XCLocalSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
4D22ABE82AF431CB00220026 /* CapApp-SPM */ = {
isa = XCSwiftPackageProductDependency;
package = D4C12C0A2AAA248700AAC8A2 /* XCLocalSwiftPackageReference "CapApp-SPM" */;
productName = "CapApp-SPM";
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 504EC2FC1FED79650016851F /* Project object */;
}
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,33 @@
{
"originHash" : "26db9e40224425527189efa269681907cff4f02aebc2d275c3ab2fb2c8ce83e1",
"pins" : [
{
"identity" : "capacitor-swift-pm",
"kind" : "remoteSourceControl",
"location" : "https://github.com/ionic-team/capacitor-swift-pm.git",
"state" : {
"revision" : "0e862e6ff13852a710c8a484180ca4d6a2cc9761",
"version" : "8.2.0"
}
},
{
"identity" : "ion-ios-filesystem",
"kind" : "remoteSourceControl",
"location" : "https://github.com/ionic-team/ion-ios-filesystem.git",
"state" : {
"revision" : "0d81e26e828ff9582807e2339112cedf2e0fab85",
"version" : "1.1.2"
}
},
{
"identity" : "transistor-background-fetch",
"kind" : "remoteSourceControl",
"location" : "https://github.com/transistorsoft/transistor-background-fetch.git",
"state" : {
"revision" : "34676021268c22c5cd356d9c96644aa0ee5fb68d",
"version" : "4.1.1"
}
}
],
"version" : 3
}
+49
View File
@@ -0,0 +1,49 @@
import UIKit
import Capacitor
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
// Called when the app was launched with a url. Feel free to add additional processing here,
// but if you want the App API to support tracking app url opens, make sure to keep this call
return ApplicationDelegateProxy.shared.application(app, open: url, options: options)
}
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
// Called when the app was launched with an activity, including Universal Links.
// Feel free to add additional processing here, but if you want the App API to support
// tracking app url opens, make sure to keep this call
return ApplicationDelegateProxy.shared.application(application, continue: userActivity, restorationHandler: restorationHandler)
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

@@ -0,0 +1,14 @@
{
"images" : [
{
"filename" : "AppIcon-512@2x.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
@@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "splash-2732x2732-2.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "splash-2732x2732-1.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "splash-2732x2732.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17132" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17105"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<imageView key="view" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Splash" id="snD-IY-ifK">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
</imageView>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="Splash" width="1366" height="1366"/>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>
+19
View File
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14111" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
</dependencies>
<scenes>
<!--Bridge View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="CAPBridgeViewController" customModule="Capacitor" sceneMemberID="viewController"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>
+51
View File
@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CAPACITOR_DEBUG</key>
<string>$(CAPACITOR_DEBUG)</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>Aulas UNICABA</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<true/>
</dict>
</plist>
+9
View File
@@ -0,0 +1,9 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/config/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
+55
View File
@@ -0,0 +1,55 @@
// swift-tools-version: 5.9
import PackageDescription
// DO NOT MODIFY THIS FILE - managed by Capacitor CLI commands
let package = Package(
name: "CapApp-SPM",
platforms: [.iOS(.v15)],
products: [
.library(
name: "CapApp-SPM",
targets: ["CapApp-SPM"])
],
dependencies: [
.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", exact: "8.2.0"),
.package(name: "CapacitorCommunityFileOpener", path: "../../../node_modules/@capacitor-community/file-opener"),
.package(name: "CapacitorCommunityScreenBrightness", path: "../../../node_modules/@capacitor-community/screen-brightness"),
.package(name: "CapacitorApp", path: "../../../node_modules/@capacitor/app"),
.package(name: "CapacitorClipboard", path: "../../../node_modules/@capacitor/clipboard"),
.package(name: "CapacitorFilesystem", path: "../../../node_modules/@capacitor/filesystem"),
.package(name: "CapacitorHaptics", path: "../../../node_modules/@capacitor/haptics"),
.package(name: "CapacitorKeyboard", path: "../../../node_modules/@capacitor/keyboard"),
.package(name: "CapacitorLocalNotifications", path: "../../../node_modules/@capacitor/local-notifications"),
.package(name: "CapacitorNetwork", path: "../../../node_modules/@capacitor/network"),
.package(name: "CapacitorPreferences", path: "../../../node_modules/@capacitor/preferences"),
.package(name: "CapacitorShare", path: "../../../node_modules/@capacitor/share"),
.package(name: "CapacitorStatusBar", path: "../../../node_modules/@capacitor/status-bar"),
.package(name: "CapawesomeCapacitorFilePicker", path: "../../../node_modules/@capawesome/capacitor-file-picker"),
.package(name: "EbarooniCapacitorCalendar", path: "../../../node_modules/@ebarooni/capacitor-calendar"),
.package(name: "TransistorsoftCapacitorBackgroundFetch", path: "../../../node_modules/@transistorsoft/capacitor-background-fetch")
],
targets: [
.target(
name: "CapApp-SPM",
dependencies: [
.product(name: "Capacitor", package: "capacitor-swift-pm"),
.product(name: "Cordova", package: "capacitor-swift-pm"),
.product(name: "CapacitorCommunityFileOpener", package: "CapacitorCommunityFileOpener"),
.product(name: "CapacitorCommunityScreenBrightness", package: "CapacitorCommunityScreenBrightness"),
.product(name: "CapacitorApp", package: "CapacitorApp"),
.product(name: "CapacitorClipboard", package: "CapacitorClipboard"),
.product(name: "CapacitorFilesystem", package: "CapacitorFilesystem"),
.product(name: "CapacitorHaptics", package: "CapacitorHaptics"),
.product(name: "CapacitorKeyboard", package: "CapacitorKeyboard"),
.product(name: "CapacitorLocalNotifications", package: "CapacitorLocalNotifications"),
.product(name: "CapacitorNetwork", package: "CapacitorNetwork"),
.product(name: "CapacitorPreferences", package: "CapacitorPreferences"),
.product(name: "CapacitorShare", package: "CapacitorShare"),
.product(name: "CapacitorStatusBar", package: "CapacitorStatusBar"),
.product(name: "CapawesomeCapacitorFilePicker", package: "CapawesomeCapacitorFilePicker"),
.product(name: "EbarooniCapacitorCalendar", package: "EbarooniCapacitorCalendar"),
.product(name: "TransistorsoftCapacitorBackgroundFetch", package: "TransistorsoftCapacitorBackgroundFetch")
]
)
]
)
+5
View File
@@ -0,0 +1,5 @@
# CapApp-SPM
This package is used to host SPM dependencies for your Capacitor project
Do not modify the contents of it or there may be unintended consequences.
@@ -0,0 +1 @@
public let isCapacitorApp = true
+1
View File
@@ -0,0 +1 @@
CAPACITOR_DEBUG = true
+12 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "aulas-unicaba", "name": "aulas-unicaba",
"version": "1.2.2", "version": "1.2.3",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "aulas-unicaba", "name": "aulas-unicaba",
"version": "1.2.2", "version": "1.2.3",
"dependencies": { "dependencies": {
"@angular/animations": "~20.3.18", "@angular/animations": "~20.3.18",
"@angular/common": "~20.3.18", "@angular/common": "~20.3.18",
@@ -24,6 +24,7 @@
"@capacitor/core": "8.2.0", "@capacitor/core": "8.2.0",
"@capacitor/filesystem": "^8.1.2", "@capacitor/filesystem": "^8.1.2",
"@capacitor/haptics": "8.0.1", "@capacitor/haptics": "8.0.1",
"@capacitor/ios": "^8.2.0",
"@capacitor/keyboard": "8.0.1", "@capacitor/keyboard": "8.0.1",
"@capacitor/local-notifications": "^8.0.2", "@capacitor/local-notifications": "^8.0.2",
"@capacitor/network": "^8.0.0", "@capacitor/network": "^8.0.0",
@@ -2865,6 +2866,15 @@
"@capacitor/core": ">=8.0.0" "@capacitor/core": ">=8.0.0"
} }
}, },
"node_modules/@capacitor/ios": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/@capacitor/ios/-/ios-8.2.0.tgz",
"integrity": "sha512-X2/VtM4qP/R1SM0VQ5W/VotEc6PS/KTooD33EijsfAHWBdee+xmBapW8SeNLnu16wJ+tsfWlvtipaJEyfKbRKQ==",
"license": "MIT",
"peerDependencies": {
"@capacitor/core": "^8.2.0"
}
},
"node_modules/@capacitor/keyboard": { "node_modules/@capacitor/keyboard": {
"version": "8.0.1", "version": "8.0.1",
"resolved": "https://registry.npmjs.org/@capacitor/keyboard/-/keyboard-8.0.1.tgz", "resolved": "https://registry.npmjs.org/@capacitor/keyboard/-/keyboard-8.0.1.tgz",
+1
View File
@@ -29,6 +29,7 @@
"@capacitor/core": "8.2.0", "@capacitor/core": "8.2.0",
"@capacitor/filesystem": "^8.1.2", "@capacitor/filesystem": "^8.1.2",
"@capacitor/haptics": "8.0.1", "@capacitor/haptics": "8.0.1",
"@capacitor/ios": "^8.2.0",
"@capacitor/keyboard": "8.0.1", "@capacitor/keyboard": "8.0.1",
"@capacitor/local-notifications": "^8.0.2", "@capacitor/local-notifications": "^8.0.2",
"@capacitor/network": "^8.0.0", "@capacitor/network": "^8.0.0",
-31
View File
@@ -171,38 +171,7 @@
</ion-card-content> </ion-card-content>
</ion-card> </ion-card>
<!-- Beneficios del Estudiante -->
<div class="section-header ion-padding-horizontal">
<h3>Beneficios del Estudiante</h3>
<p>Aprovechá convenios exclusivos por ser parte de UNICABA.</p>
</div>
<ion-row class="benefits-row">
<ion-col size="6">
<ion-card class="benefit-card" (click)="showBenefitDetail('soy')">
<div class="benefit-icon-container soy">
<ion-icon name="school-outline"></ion-icon>
</div>
<ion-card-content>
<h4>SOY Estudiante</h4>
<p>Descuentos en pasajes de micro.</p>
<ion-badge color="success">Activo</ion-badge>
</ion-card-content>
</ion-card>
</ion-col>
<ion-col size="6">
<ion-card class="benefit-card" (click)="showBenefitDetail('google')">
<div class="benefit-icon-container google">
<ion-icon name="logo-google" color="primary"></ion-icon>
</div>
<ion-card-content>
<h4>Google Students</h4>
<p>Gemini PRO & Drive 1TB.</p>
<ion-badge color="tertiary">Bonificado</ion-badge>
</ion-card-content>
</ion-card>
</ion-col>
</ion-row>
<div class="security-info ion-padding"> <div class="security-info ion-padding">
-92
View File
@@ -195,81 +195,6 @@
font-weight: 600; font-weight: 600;
} }
.section-header {
margin-top: 24px;
h3 {
margin: 0;
font-size: 1.2rem;
font-weight: 700;
color: var(--ion-color-dark);
}
p {
margin: 4px 0 0;
font-size: 0.9rem;
color: var(--ion-color-medium);
}
}
.benefits-row {
padding: 0 8px;
margin-top: -8px;
}
.benefit-card {
margin: 8px;
border-radius: 16px;
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
display: flex;
flex-direction: column;
height: calc(100% - 16px);
transition: transform 0.2s;
overflow: hidden;
&:active {
transform: scale(0.96);
}
.benefit-icon-container {
height: 80px;
display: flex;
align-items: center;
justify-content: center;
&.soy {
background: linear-gradient(135deg, #f0f7ff 0%, #e0e7ff 100%);
ion-icon { font-size: 40px; color: #1a73e8; }
}
&.google {
background: linear-gradient(135deg, #fff5f5 0%, #ffe0e0 100%);
ion-icon { font-size: 40px; }
}
}
ion-card-content {
padding: 12px;
text-align: center;
h4 {
margin: 0;
font-size: 0.95rem;
font-weight: 700;
color: var(--ion-color-dark);
}
p {
margin: 6px 0;
font-size: 0.75rem;
color: var(--ion-color-medium);
line-height: 1.2;
}
ion-badge {
font-size: 0.65rem;
padding: 4px 8px;
border-radius: 6px;
}
}
}
.security-info { .security-info {
margin-top: 24px; margin-top: 24px;
display: flex; display: flex;
@@ -290,20 +215,3 @@
max-width: 280px; max-width: 280px;
} }
} }
:host ::ng-deep .benefit-alert {
.alert-message {
text-align: left;
}
.benefit-modal-content {
b { color: var(--ion-color-primary); }
ol {
padding-left: 20px;
margin-top: 10px;
}
li {
margin-bottom: 8px;
font-size: 0.9rem;
}
}
}
+1 -9
View File
@@ -202,15 +202,7 @@ export class PerfilPage implements OnInit, OnDestroy {
} }
} }
async showBenefitDetail(type: 'soy' | 'google') {
const modal = await this.modalCtrl.create({
component: InfoModalComponent,
componentProps: {
selectedSegment: 'benefits'
}
});
await modal.present();
}
// --- NUEVAS FUNCIONES CONSOLIDADAS --- // --- NUEVAS FUNCIONES CONSOLIDADAS ---
+184 -40
View File
@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { CapacitorHttp, HttpResponse } from '@capacitor/core'; import { CapacitorHttp, CapacitorCookies, HttpResponse } from '@capacitor/core';
import { Filesystem, Directory } from '@capacitor/filesystem'; import { Filesystem, Directory } from '@capacitor/filesystem';
import { FileOpener } from '@capacitor-community/file-opener'; import { FileOpener } from '@capacitor-community/file-opener';
import { AuthService } from './auth.service'; import { AuthService } from './auth.service';
@@ -11,16 +11,16 @@ export class SiuService {
async getAcademicHistory(dni: string, pass: string, carreraId: string = '30'): Promise<any[]> { async getAcademicHistory(dni: string, pass: string, carreraId: string = '30'): Promise<any[]> {
try { try {
await this.authenticateSiu(dni, pass); const authCookies = await this.authenticateSiu(dni, pass);
await this.selectCareer(carreraId); const sessionCookies = await this.selectCareer(carreraId, authCookies);
// 3. OBTENER PLAN DE ESTUDIO // 3. OBTENER PLAN DE ESTUDIO
const resPlan: HttpResponse = await CapacitorHttp.get({ const { html: rawHtml } = await this.getHtml(`${this.BASE_URL}/plan_estudio`, sessionCookies);
url: `${this.BASE_URL}/plan_estudio`,
}); if (!rawHtml) {
throw new Error('No se recibió contenido del plan de estudios.');
}
// 4. PARSEO DEL JSON EMBEBIDO
const rawHtml = resPlan.data;
const regex = /on_arrival\((\{.*?"id":"info_plan".*?\})\);/s; const regex = /on_arrival\((\{.*?"id":"info_plan".*?\})\);/s;
const match = rawHtml.match(regex); const match = rawHtml.match(regex);
@@ -30,6 +30,10 @@ export class SiuService {
return this.parseHtmlSubjects(contentHtml); return this.parseHtmlSubjects(contentHtml);
} }
if (rawHtml.includes('acceso?auth=form') || rawHtml.includes('id="login"')) {
throw new Error('La sesión de SIU Guaraní no se pudo autenticar o expiró.');
}
throw new Error('No se encontró el bloque de materias'); throw new Error('No se encontró el bloque de materias');
} catch (error) { } catch (error) {
console.error('Error SIU Academic History:', error); console.error('Error SIU Academic History:', error);
@@ -39,15 +43,13 @@ export class SiuService {
async getInscripcionesActivas(dni: string, pass: string, carreraId: string = '30'): Promise<any[]> { async getInscripcionesActivas(dni: string, pass: string, carreraId: string = '30'): Promise<any[]> {
try { try {
await this.authenticateSiu(dni, pass); const authCookies = await this.authenticateSiu(dni, pass);
await this.selectCareer(carreraId); const sessionCookies = await this.selectCareer(carreraId, authCookies);
// OBTENER PÁGINA DE CURSADA // OBTENER PÁGINA DE CURSADA
const resCursada: HttpResponse = await CapacitorHttp.get({ const { html: rawHtml } = await this.getHtml(`${this.BASE_URL}/cursada`, sessionCookies);
url: `${this.BASE_URL}/cursada`,
});
const rawHtml = resCursada.data; if (!rawHtml) return [];
// 1. Extraer directamente de "notificaciones" donde SIU provee el array JSON "asignaciones" // 1. Extraer directamente de "notificaciones" donde SIU provee el array JSON "asignaciones"
const regexNotif = /on_arrival\((\{.*?"id":"notificaciones".*?\})\);/s; const regexNotif = /on_arrival\((\{.*?"id":"notificaciones".*?\})\);/s;
@@ -86,25 +88,110 @@ export class SiuService {
} }
} }
private async authenticateSiu(dni: string, pass: string): Promise<void> { private async authenticateSiu(dni: string, pass: string): Promise<string> {
console.log('Autenticando en SIU...'); console.log('Autenticando en SIU...');
const loginData = `usuario=${dni}&password=${pass}&login=Ingresar`; const loginData = `usuario=${encodeURIComponent(dni)}&password=${encodeURIComponent(pass)}&login=Ingresar`;
await CapacitorHttp.post({ const headers = this.getCommonHeaders();
headers['Content-Type'] = 'application/x-www-form-urlencoded';
const resLogin: HttpResponse = await CapacitorHttp.post({
url: `${this.BASE_URL}/acceso?auth=form`, url: `${this.BASE_URL}/acceso?auth=form`,
data: loginData, data: loginData,
headers: { 'Content-Type': 'application/x-www-form-urlencoded' } headers
}); });
let cookies = this.extractCookies(resLogin.headers);
const stored = await this.getStoredCookies();
cookies = this.mergeCookieStrings(cookies, stored);
const dataStr = this.decodeResponseData(resLogin.data);
if (dataStr && (dataStr.includes('El usuario o la contraseña no son correctos') || dataStr.includes('error-login') || dataStr.includes('login_error'))) {
throw new Error('DNI o Contraseña incorrectos en SIU Guaraní.');
}
return cookies;
} }
private async selectCareer(carreraId: string): Promise<void> { private async selectCareer(carreraId: string, currentCookies: string): Promise<string> {
console.log('Seleccionando carrera:', carreraId); console.log('Seleccionando carrera:', carreraId);
await CapacitorHttp.get({ const resCambio = await this.getHtml(`${this.BASE_URL}/acceso/cambiar_carrera?id=${carreraId}&op=inicio_alumno`, currentCookies);
url: `${this.BASE_URL}/acceso/cambiar_carrera?id=${carreraId}&op=inicio_alumno`, const resInicio = await this.getHtml(`${this.BASE_URL}/inicio_alumno`, resCambio.cookies);
}); return resInicio.cookies;
// Hit inicio_alumno to ensure context is established }
await CapacitorHttp.get({
url: `${this.BASE_URL}/inicio_alumno`, private async getHtml(url: string, cookies: string): Promise<{ html: string; cookies: string }> {
let updatedCookies = cookies;
const res: HttpResponse = await CapacitorHttp.get({
url,
headers: this.getCommonHeaders(updatedCookies)
}); });
updatedCookies = this.mergeCookies(updatedCookies, res.headers);
const stored = await this.getStoredCookies();
updatedCookies = this.mergeCookieStrings(updatedCookies, stored);
let html = this.decodeResponseData(res.data);
// Fallback: si html es nulo/vacío por codificación no-UTF8 (ej. ISO-8859-1 en SIU), solicitar arraybuffer y decodificar
if (!html) {
console.log(`Reintentando ${url} con arraybuffer para decodificación ISO-8859-1...`);
const resBuffer: HttpResponse = await CapacitorHttp.get({
url,
responseType: 'arraybuffer',
headers: this.getCommonHeaders(updatedCookies)
});
updatedCookies = this.mergeCookies(updatedCookies, resBuffer.headers);
html = this.decodeBase64Response(resBuffer.data);
}
return { html, cookies: updatedCookies };
}
private decodeResponseData(data: any): string {
if (typeof data === 'string') {
return data;
}
if (data && typeof data === 'object') {
return JSON.stringify(data);
}
return '';
}
private decodeBase64Response(base64: string): string {
if (!base64 || typeof base64 !== 'string') return '';
try {
const binaryString = window.atob(base64);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
try {
const utf8Decoder = new TextDecoder('utf-8', { fatal: true });
return utf8Decoder.decode(bytes);
} catch {
const latinDecoder = new TextDecoder('iso-8859-1');
return latinDecoder.decode(bytes);
}
} catch (e) {
console.error('Error decoding base64 response:', e);
return '';
}
}
private async getStoredCookies(): Promise<string> {
try {
const cookieMap = await CapacitorCookies.getCookies({ url: this.BASE_URL });
if (cookieMap && typeof cookieMap === 'object') {
return Object.entries(cookieMap)
.filter(([k, v]) => k && v)
.map(([k, v]) => `${k}=${v}`)
.join('; ');
}
} catch (e) {
// CapacitorCookies can fail gracefully without breaking flow
}
return '';
} }
private extractTobaContent(html: string, pageletId: string = 'pagelet'): string { private extractTobaContent(html: string, pageletId: string = 'pagelet'): string {
@@ -132,34 +219,30 @@ export class SiuService {
async downloadCertificate(dni: string, pass: string): Promise<{ base64: string, filename: string }> { async downloadCertificate(dni: string, pass: string): Promise<{ base64: string, filename: string }> {
console.log('Iniciando descarga de certificado para:', dni); console.log('Iniciando descarga de certificado para:', dni);
try { try {
await this.authenticateSiu(dni, pass); const authCookies = await this.authenticateSiu(dni, pass);
await this.selectCareer('30'); const sessionCookies = await this.selectCareer('30', authCookies);
console.log('Login y selección de carrera (ID 30) listos'); console.log('Login y selección de carrera (ID 30) listos');
// 1. VALIDAR SI EXISTE UNO VIGENTE (< 30 DÍAS) // 1. VALIDAR SI EXISTE UNO VIGENTE (< 30 DÍAS)
const resSolicitudes: HttpResponse = await CapacitorHttp.get({ const { html: rawHtmlSolicitudes, cookies: solicitCookies } = await this.getHtml(`${this.BASE_URL}/solicitudes`, sessionCookies);
url: `${this.BASE_URL}/solicitudes`,
});
console.log('Página de solicitudes obtenida'); console.log('Página de solicitudes obtenida');
const contentHtmlSolicitudes = this.extractTobaContent(resSolicitudes.data, 'pagelet'); const contentHtmlSolicitudes = this.extractTobaContent(rawHtmlSolicitudes, 'pagelet');
if (contentHtmlSolicitudes) { if (contentHtmlSolicitudes) {
const existingCert = this.findValidCertificate(contentHtmlSolicitudes); const existingCert = this.findValidCertificate(contentHtmlSolicitudes);
if (existingCert) { if (existingCert) {
console.log('Certificado vigente encontrado:', existingCert.filename); console.log('Certificado vigente encontrado:', existingCert.filename);
return await this.downloadFile(existingCert.url, existingCert.filename); return await this.downloadFile(existingCert.url, existingCert.filename, solicitCookies);
} }
} }
// 2. GENERAR NUEVA SOLICITUD (HEADLESS) // 2. GENERAR NUEVA SOLICITUD (HEADLESS)
console.log('No hay certificado vigente o no se pudo leer la tabla. Intentando generar uno nuevo...'); console.log('No hay certificado vigente o no se pudo leer la tabla. Intentando generar uno nuevo...');
const resGestion: HttpResponse = await CapacitorHttp.get({ const { html: rawHtmlGestion, cookies: gestionCookies } = await this.getHtml(`${this.BASE_URL}/solicitudes/solicitud_gestion`, solicitCookies);
url: `${this.BASE_URL}/solicitudes/solicitud_gestion`,
});
const contentHtmlGestion = this.extractTobaContent(resGestion.data, 'pagelet'); const contentHtmlGestion = this.extractTobaContent(rawHtmlGestion, 'pagelet');
if (!contentHtmlGestion) { if (!contentHtmlGestion) {
console.error('HTML completo de gestión no contiene on_arrival de pagelet. Muestra:', resGestion.data.substring(0, 1000)); console.error('HTML completo de gestión no contiene on_arrival de pagelet. Muestra:', rawHtmlGestion.substring(0, 1000));
throw new Error('No se pudo localizar el generador (content missing)'); throw new Error('No se pudo localizar el generador (content missing)');
} }
@@ -186,7 +269,7 @@ export class SiuService {
: `${iframeSrc}?toba_ei_notificar=imprimir`; : `${iframeSrc}?toba_ei_notificar=imprimir`;
console.log('Solicitando generación de PDF:', printUrl); console.log('Solicitando generación de PDF:', printUrl);
return await this.downloadFile(printUrl, `Certificado_Alumno_Regular_${new Date().getTime()}.pdf`); return await this.downloadFile(printUrl, `Certificado_Alumno_Regular_${new Date().getTime()}.pdf`, gestionCookies);
} catch (error) { } catch (error) {
console.error('Error detallado en SIU Certificate:', error); console.error('Error detallado en SIU Certificate:', error);
@@ -237,13 +320,14 @@ export class SiuService {
return days < 30; return days < 30;
} }
private async downloadFile(url: string, filename: string): Promise<{ base64: string, filename: string }> { private async downloadFile(url: string, filename: string, cookies?: string): Promise<{ base64: string, filename: string }> {
// Si la URL es relativa, completarla // Si la URL es relativa, completarla
const fullUrl = url.startsWith('http') ? url : `${this.BASE_URL}/${url.startsWith('/') ? url.substring(1) : url}`; const fullUrl = url.startsWith('http') ? url : `${this.BASE_URL}/${url.startsWith('/') ? url.substring(1) : url}`;
const response: HttpResponse = await CapacitorHttp.get({ const response: HttpResponse = await CapacitorHttp.get({
url: fullUrl, url: fullUrl,
responseType: 'blob' responseType: 'blob',
headers: this.getCommonHeaders(cookies)
}); });
return { return {
@@ -252,6 +336,66 @@ export class SiuService {
}; };
} }
private getCommonHeaders(cookies?: string): Record<string, string> {
const headers: Record<string, string> = {
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'es-AR,es;q=0.9'
};
if (cookies) {
headers['Cookie'] = cookies;
}
return headers;
}
private extractCookies(headers: Record<string, string> | undefined): string {
return this.mergeCookies('', headers);
}
private mergeCookies(existingCookies: string, newHeaders: Record<string, string> | undefined): string {
if (!newHeaders) return existingCookies;
const rawSetCookie: string = newHeaders['set-cookie'] || newHeaders['Set-Cookie'] || '';
if (!rawSetCookie) return existingCookies;
const cookieMap = new Map<string, string>();
if (existingCookies) {
existingCookies.split(';').forEach(c => {
const [k, ...v] = c.trim().split('=');
if (k) cookieMap.set(k.trim(), v.join('='));
});
}
const newCookies = rawSetCookie.split(/,(?=[^ ])/);
newCookies.forEach(c => {
const cookiePart = c.trim().split(';')[0];
const [k, ...v] = cookiePart.split('=');
if (k && v.length > 0) {
cookieMap.set(k.trim(), v.join('='));
}
});
return Array.from(cookieMap.entries())
.map(([k, v]) => `${k}=${v}`)
.join('; ');
}
private mergeCookieStrings(cookies1: string, cookies2: string): string {
if (!cookies1) return cookies2 || '';
if (!cookies2) return cookies1 || '';
const cookieMap = new Map<string, string>();
cookies1.split(';').forEach(c => {
const [k, ...v] = c.trim().split('=');
if (k) cookieMap.set(k.trim(), v.join('='));
});
cookies2.split(';').forEach(c => {
const [k, ...v] = c.trim().split('=');
if (k) cookieMap.set(k.trim(), v.join('='));
});
return Array.from(cookieMap.entries())
.map(([k, v]) => `${k}=${v}`)
.join('; ');
}
async saveAndOpenFile(base64Data: string, filename: string): Promise<void> { async saveAndOpenFile(base64Data: string, filename: string): Promise<void> {
try { try {
const savedFile = await Filesystem.writeFile({ const savedFile = await Filesystem.writeFile({