feat(pos): Capacitor mobile app setup — Android + iOS wrapper

Set up Capacitor to wrap the Nexus POS web app as a native mobile app.
The app loads from the remote server URL so no bundling is needed.
Includes native-bridge.js for camera, push notifications, haptics,
and status bar integration when running inside the native shell.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-04 02:51:07 +00:00
parent db5bbf6718
commit 2463c2fbcf
10 changed files with 1321 additions and 0 deletions

4
pos/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
node_modules/
android/
ios/
.gradle/

17
pos/capacitor.config.json Normal file
View File

@@ -0,0 +1,17 @@
{
"appId": "com.nexusautoparts.pos",
"appName": "Nexus POS",
"webDir": "www",
"server": {
"url": "https://nexus.consultoria-as.com/pos",
"cleartext": true
},
"plugins": {
"SplashScreen": {
"launchShowDuration": 2000,
"backgroundColor": "#0d0d0d",
"showSpinner": true,
"spinnerColor": "#F5A623"
}
}
}

50
pos/mobile/README.md Normal file
View File

@@ -0,0 +1,50 @@
# Nexus POS — Mobile App Build Guide
## Prerequisites
- Node.js 18+
- Android Studio (for Android)
- Xcode 15+ (for iOS, macOS only)
## Architecture
The Capacitor app loads the POS from the remote server at
`https://nexus.consultoria-as.com/pos`. This means:
- The app requires internet on first load.
- The PWA service worker handles offline caching after that.
- No HTML/JS/CSS is bundled into the native binary.
## Android Build
```bash
cd /home/Autopartes/pos
npx cap sync android
npx cap open android
# In Android Studio: Build > Generate Signed APK
```
## iOS Build
```bash
cd /home/Autopartes/pos
npx cap sync ios
npx cap open ios
# In Xcode: Product > Archive
```
## Development
```bash
npx cap run android --livereload --external
npx cap run ios --livereload --external
```
## App Icons & Splash Screens
Place source images in `mobile/resources/`:
- `icon.png` — 1024x1024 app icon
- `splash.png` — 2732x2732 splash screen
Then generate platform assets:
```bash
npx @capacitor/assets generate
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

1135
pos/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

24
pos/package.json Normal file
View File

@@ -0,0 +1,24 @@
{
"name": "pos",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs",
"dependencies": {
"@capacitor/android": "^8.3.0",
"@capacitor/camera": "^8.0.2",
"@capacitor/cli": "^7.6.1",
"@capacitor/core": "^8.3.0",
"@capacitor/haptics": "^8.0.2",
"@capacitor/ios": "^8.3.0",
"@capacitor/push-notifications": "^8.0.3",
"@capacitor/splash-screen": "^8.0.1",
"@capacitor/status-bar": "^8.0.2"
}
}

View File

@@ -0,0 +1,72 @@
// native-bridge.js — Detects if running inside Capacitor native app
// and provides native API access (camera for barcode, push notifications, haptics)
(function() {
'use strict';
window.NexusNative = {
isNative: typeof Capacitor !== 'undefined',
// Camera for barcode scanning
async scanBarcode() {
if (!this.isNative) return null;
try {
const { Camera } = await import('@capacitor/camera');
const photo = await Camera.getPhoto({
quality: 90,
resultType: 'base64'
});
// In production, send to a barcode decode service
return photo;
} catch(e) {
return null;
}
},
// Push notification registration
async registerPush() {
if (!this.isNative) return null;
try {
const { PushNotifications } = await import('@capacitor/push-notifications');
const result = await PushNotifications.requestPermissions();
if (result.receive === 'granted') {
await PushNotifications.register();
}
PushNotifications.addListener('registration', token => {
console.log('Push token:', token.value);
// Send token to server for this employee
});
PushNotifications.addListener('pushNotificationReceived', notification => {
console.log('Push received:', notification);
});
} catch(e) {
console.log('Push not available:', e);
}
},
// Haptic feedback
async vibrate() {
if (!this.isNative) return;
try {
const { Haptics, ImpactStyle } = await import('@capacitor/haptics');
await Haptics.impact({ style: ImpactStyle.Light });
} catch(e) {}
},
// Status bar (hide for fullscreen POS)
async setupStatusBar() {
if (!this.isNative) return;
try {
const { StatusBar, Style } = await import('@capacitor/status-bar');
await StatusBar.setStyle({ style: Style.Dark });
await StatusBar.setBackgroundColor({ color: '#0d0d0d' });
} catch(e) {}
}
};
// Auto-init if native
if (window.NexusNative.isNative) {
window.NexusNative.setupStatusBar();
window.NexusNative.registerPush();
}
})();

View File

@@ -10,6 +10,7 @@
<link rel="stylesheet" href="/pos/static/css/onboarding.css" />
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
<meta name="theme-color" content="#F5A623" />
<script src="/pos/static/js/native-bridge.js"></script>
<style>
/* =========================================================================

View File

@@ -9,6 +9,7 @@
<link rel="stylesheet" href="/pos/static/css/chat.css" />
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
<meta name="theme-color" content="#F5A623" />
<script src="/pos/static/js/native-bridge.js"></script>
<style>
/* =====================================================================

17
pos/www/index.html Normal file
View File

@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nexus POS</title>
<style>
body { background: #0d0d0d; color: #F5A623; font-family: sans-serif;
display: flex; align-items: center; justify-content: center;
height: 100vh; margin: 0; }
p { text-align: center; }
</style>
</head>
<body>
<p>Cargando Nexus POS&hellip;<br><small>Conectando al servidor</small></p>
</body>
</html>