feat(login): campo de dirección del servidor configurable

- config.ts: BASE_URL/ORIGIN dejan de ser constantes; ahora hay
  setServerOrigin/getBaseUrl/getServerOrigin con normalizeServerUrl
  (añade http:// si falta, quita barras finales y el sufijo /api/v1).
- client.ts: construye las URLs con getBaseUrl() en cada petición.
- session: signIn recibe la URL, la fija antes del login y la persiste
  en SecureStore (clave server_url); se rehidrata al arrancar junto al token.
- LoginScreen: campo "Servidor" precargado con el último valor usado.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-07-08 10:00:13 +02:00
co-authored by Claude Sonnet 4.6
parent 04859c281d
commit 9c4c3b54ae
4 changed files with 90 additions and 23 deletions
+19 -4
View File
@@ -15,7 +15,8 @@ import { useSession } from '../auth/session';
const DEVICE_NAME = `${Platform.OS}-avante`;
export function LoginScreen() {
const { signIn } = useSession();
const { signIn, serverUrl } = useSession();
const [server, setServer] = useState(serverUrl);
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false);
@@ -25,7 +26,7 @@ export function LoginScreen() {
setError(null);
setLoading(true);
try {
await signIn(email.trim(), password, DEVICE_NAME);
await signIn(email.trim(), password, DEVICE_NAME, server);
} catch (e) {
if (e instanceof ApiError && e.status === 422) {
setError('Credenciales inválidas.');
@@ -37,6 +38,8 @@ export function LoginScreen() {
}
};
const canSubmit = !loading && !!email && !!password && !!server.trim();
return (
<KeyboardAvoidingView
style={styles.container}
@@ -44,6 +47,17 @@ export function LoginScreen() {
>
<Text style={styles.title}>Avante · Seguimiento de obra</Text>
<Text style={styles.fieldLabel}>Servidor</Text>
<TextInput
style={styles.input}
placeholder="https://servidor o IP (p.ej. 192.168.1.50/construprogress/public)"
autoCapitalize="none"
autoCorrect={false}
keyboardType="url"
value={server}
onChangeText={setServer}
/>
<TextInput
style={styles.input}
placeholder="Email"
@@ -63,9 +77,9 @@ export function LoginScreen() {
{error && <Text style={styles.error}>{error}</Text>}
<TouchableOpacity
style={[styles.button, (loading || !email || !password) && styles.buttonDisabled]}
style={[styles.button, !canSubmit && styles.buttonDisabled]}
onPress={onSubmit}
disabled={loading || !email || !password}
disabled={!canSubmit}
>
{loading ? (
<ActivityIndicator color="#fff" />
@@ -80,6 +94,7 @@ export function LoginScreen() {
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', padding: 24, gap: 12 },
title: { fontSize: 22, fontWeight: '700', textAlign: 'center', marginBottom: 16 },
fieldLabel: { fontSize: 12, color: '#666', marginBottom: -6, fontWeight: '600' },
input: {
borderWidth: 1,
borderColor: '#ccc',