react-native-qalink 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,354 +1,483 @@
1
1
  # react-native-qalink 🔍
2
2
 
3
- SDK de captura de errores en tiempo real para React Native. Se instala en la app y transmite al servidor QALink cada request de red, error JS, log de consola y error del runtime — permitiendo al equipo de QA ver en tiempo real si un bug es del **frontend** o del **backend**, sin necesidad de Android Studio ni logs técnicos.
3
+ SDK de captura de errores en tiempo real para React Native. Captura errores, logs, eventos custom, requests HTTP y screenshots — permitiendo al equipo de QA ver en tiempo real qué está pasando en la app, sin necesidad de Android Studio ni logs técnicos.
4
4
 
5
- La conexión WebSocket apunta siempre a **wss://ws.airbites.org**; no es necesario configurar la URL.
5
+ La conexión WebSocket apunta siempre a **wss://ws.airbites.org**.
6
6
 
7
7
  ---
8
8
 
9
- ## ¿Cómo identifica los requests a la API?
9
+ ## 📦 Instalación
10
10
 
11
- La librería usa **dos estrategias de interceptación simultáneas**:
11
+ ```bash
12
+ npm install react-native-qalink
13
+ # o
14
+ yarn add react-native-qalink
15
+ ```
12
16
 
13
- ### 1. Monkey-patch de `fetch` global
14
- Al llamar `QALink.init()`, la librería reemplaza el `fetch` nativo del entorno JavaScript por su propia versión instrumentada. Cualquier llamada a `fetch(...)` en toda la app pasa primero por QALink, que registra el request y la respuesta antes de devolver el control al código original.
17
+ ### Dependencia opcional para capturas de pantalla:
15
18
 
19
+ ```bash
20
+ npm install react-native-view-shot
21
+ cd ios && pod install
16
22
  ```
17
- Tu código → fetch('/api/users')
18
-
19
- QALink intercepta → registra método, URL, tiempo de inicio
20
-
21
- Ejecuta fetch real → espera respuesta del servidor
22
-
23
- QALink registra → status code, body, duración, clasificación
24
-
25
- Devuelve al código → response (sin modificarla)
26
- ```
27
-
28
- ### 2. Interceptores de Axios
29
- Axios expone un sistema oficial de interceptores (`.interceptors.request` y `.interceptors.response`). QALink se engancha en ambos para capturar cada request antes de que salga y cada respuesta antes de que llegue al código de la app. Funciona con la instancia global y con instancias personalizadas.
30
23
 
31
24
  ---
32
25
 
33
- ## ¿Cómo captura los logs de Metro y el runtime de React Native?
26
+ ## 🚀 Inicio rápido
34
27
 
35
- Hay tres capas adicionales que se activan automáticamente con `init()`:
28
+ ```typescript
29
+ import QALink from 'react-native-qalink';
36
30
 
37
- ### 3. Monkey-patch de `console`
38
- QALink reemplaza `console.log`, `console.warn` y `console.error` con versiones instrumentadas. **Metro sigue mostrando todo normalmente** (se llama al original primero), pero cada mensaje se envía también al dashboard. Los mensajes se clasifican automáticamente:
31
+ // 1. Inicializar el SDK
32
+ QALink.init({
33
+ apiKey: 'qlk_your_api_key_here',
34
+ appVersion: '1.0.0',
35
+ environment: 'dev',
36
+ debug: true,
37
+ });
39
38
 
40
- ```
41
- console.log('Productos cargados', data) → 🔵 user_log
42
- console.warn('componentWillMount deprecated') → 🟡 rn_warning
43
- console.error('Invariant Violation: ...') → 🔴 rn_error
44
- console.error('Unable to resolve module') → 🔴 rn_error (Metro)
39
+ // 2. Configurar contexto de usuario
40
+ QALink.setUserContext({
41
+ userId: '12345',
42
+ email: 'user@example.com',
43
+ plan: 'premium'
44
+ });
45
+
46
+ // 3. Configurar contexto custom
47
+ QALink.setCustomContext({
48
+ experimentVariant: 'new_checkout_flow',
49
+ featureFlags: { darkMode: true }
50
+ });
51
+
52
+ // 4. Usar los nuevos métodos de logging
53
+ QALink.debug('User opened settings');
54
+ QALink.info('Payment initiated', { amount: 99.99 });
55
+ QALink.warn('Slow API response', { duration: 3500 });
56
+ QALink.error('Payment failed', { code: 'CARD_DECLINED' });
57
+ QALink.critical('App about to crash');
58
+
59
+ // 5. Eventos custom
60
+ QALink.logEvent('checkout_completed', {
61
+ items: 3,
62
+ totalAmount: 299.99,
63
+ paymentMethod: 'credit_card'
64
+ });
65
+
66
+ // 6. Captura de pantalla (requiere react-native-view-shot)
67
+ await QALink.captureScreen('before_payment');
45
68
  ```
46
69
 
47
- ### 4. `ErrorUtils.setGlobalHandler` — Pantalla roja (Red Screen)
48
- React Native expone `ErrorUtils` para capturar errores **antes** de que la app crashee y muestre la pantalla roja. QALink se registra ahí para enviar el error al dashboard con todo el stack trace, y luego llama al handler original para que RN siga comportándose normalmente.
70
+ ---
49
71
 
72
+ ## 🔧 Configuración HTTP avanzada
73
+
74
+ El SDK captura automáticamente todas las peticiones HTTP (fetch y axios). Puedes configurar qué capturar:
75
+
76
+ ```typescript
77
+ QALink.init({
78
+ apiKey: 'qlk_xxx',
79
+ appVersion: '1.0.0',
80
+ httpConfig: {
81
+ // Captura requests y responses
82
+ captureRequests: true,
83
+ captureResponses: true,
84
+
85
+ // Filtrar por URL
86
+ includeUrls: ['/api/payment', '/api/checkout'],
87
+ excludeUrls: ['/api/analytics', '/health'],
88
+
89
+ // Filtrar por método HTTP
90
+ methods: ['POST', 'PUT', 'DELETE'], // ignorar GETs
91
+
92
+ // Sanitización de datos sensibles
93
+ sanitizeHeaders: ['Authorization', 'X-API-Key'],
94
+ sanitizeRequestBody: ['password', 'creditCard', 'cvv'],
95
+ sanitizeResponseBody: ['token', 'secret'],
96
+
97
+ // Capturar solo errores HTTP (4xx, 5xx)
98
+ captureOnlyErrors: true,
99
+ minStatusCode: 400,
100
+
101
+ // Límite de tamaño de body
102
+ maxBodySize: 5000, // bytes
103
+
104
+ // Performance monitoring
105
+ captureTimings: true,
106
+ slowRequestThreshold: 2000, // marcar requests > 2s como lentos
107
+ }
108
+ });
50
109
  ```
51
- Error fatal en JS
52
-
53
- QALink captura → envía al dashboard con stack trace completo
54
-
55
- ErrorUtils original → React Native muestra la pantalla roja
110
+
111
+ ### Actualizar configuración HTTP en runtime:
112
+
113
+ ```typescript
114
+ QALink.configureHTTP({
115
+ captureOnlyErrors: true,
116
+ slowRequestThreshold: 1000
117
+ });
56
118
  ```
57
119
 
58
- ### 5. LogBox — Cajas amarillas (Yellow Box)
59
- En React Native ≥ 0.64, los warnings se manejan internamente via `LogBox`. QALink se engancha al método interno `__warn` de LogBox para capturar cada warning antes de que aparezca en pantalla.
120
+ ---
60
121
 
61
- ### Clasificación de errores de runtime
122
+ ## 📝 API de Logging
62
123
 
63
- | Tipo | Categoría | Ejemplo |
64
- |--------------------------|-----------------|----------------------------------------------|
65
- | Error fatal | `red_screen` | `Invariant Violation`, `undefined is not...` |
66
- | Warning de RN | `yellow_box` | `componentWillMount is deprecated` |
67
- | Módulo nativo | `native_module` | `NativeModule X is null` |
68
- | Bridge JS↔Native | `bridge` | `RCTBridge error` |
69
- | Promise sin .catch() | `unknown` | `Unhandled Promise Rejection` |
70
- | Error de Metro/bundler | `rn_error` | `Unable to resolve module`, `SyntaxError` |
124
+ Todos los métodos de logging hacen **console.log nativo** (visible en Metro) **Y** envían al servidor QALink:
71
125
 
72
- ---
126
+ ```typescript
127
+ // Acepta múltiples argumentos como console.log
128
+ QALink.debug('User action', { screen: 'Settings' }, 'Extra data');
73
129
 
74
- ## Instalación
130
+ QALink.info('API call started', {
131
+ endpoint: '/api/users',
132
+ method: 'GET'
133
+ });
75
134
 
76
- ```bash
77
- npm install react-native-qalink
78
- # o
79
- yarn add react-native-qalink
135
+ QALink.warn('Memory warning', {
136
+ available: '50MB',
137
+ threshold: '100MB'
138
+ });
139
+
140
+ QALink.error('Validation failed', {
141
+ field: 'email',
142
+ reason: 'Invalid format'
143
+ });
144
+
145
+ QALink.critical('Unrecoverable error', {
146
+ code: 'FATAL_001',
147
+ message: 'Cannot connect to database'
148
+ });
80
149
  ```
81
150
 
151
+ **Niveles de log:**
152
+ - `debug` → desarrollo y debugging
153
+ - `info` → información general
154
+ - `warn` → advertencias y problemas no críticos
155
+ - `error` → errores recuperables
156
+ - `critical` → errores fatales que pueden causar crash
157
+
82
158
  ---
83
159
 
84
- ## Uso rápido
160
+ ## 🎯 Eventos Custom
85
161
 
86
- ```tsx
87
- import { QALink } from 'react-native-qalink';
88
- import axios from 'axios';
162
+ Registra eventos de negocio importantes:
89
163
 
90
- // En el arranque de la app (p. ej. App.tsx useEffect)
91
- await QALink.init({
92
- apiKey: 'qlk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
93
- appVersion: '1.2.3',
94
- environment: 'dev',
95
- debug: __DEV__,
164
+ ```typescript
165
+ // Evento simple
166
+ QALink.logEvent('user_login');
167
+
168
+ // Evento con datos
169
+ QALink.logEvent('purchase_completed', {
170
+ productId: 'SKU-123',
171
+ price: 29.99,
172
+ currency: 'USD',
173
+ quantity: 2
96
174
  });
97
175
 
98
- QALink.interceptAxios(axios);
176
+ QALink.logEvent('feature_used', {
177
+ featureName: 'dark_mode',
178
+ enabled: true,
179
+ timestamp: Date.now()
180
+ });
99
181
  ```
100
182
 
101
- No hace falta pasar `serverUrl`: la conexión usa siempre **wss://ws.airbites.org**.
183
+ **Los eventos incluyen automáticamente:**
184
+ - User context (userId, email, etc.)
185
+ - Custom context (feature flags, experiments)
186
+ - Screen actual
187
+ - Session ID
188
+ - Device ID
189
+ - Timestamp
102
190
 
103
191
  ---
104
192
 
105
- ## API pública
193
+ ## 👤 Context API
106
194
 
107
- Todo lo que puede usar quien instala la librería:
195
+ ### User Context
196
+ Información del usuario que se adjunta a **todos** los eventos:
197
+
198
+ ```typescript
199
+ QALink.setUserContext({
200
+ userId: '12345',
201
+ email: 'john@example.com',
202
+ username: 'john_doe',
203
+ plan: 'premium',
204
+ region: 'LATAM'
205
+ });
206
+
207
+ // Actualizar parcialmente
208
+ QALink.setUserContext({ plan: 'enterprise' });
209
+
210
+ // Limpiar
211
+ QALink.clearContext();
212
+ ```
108
213
 
109
- | Método | Descripción |
110
- |--------|-------------|
111
- | **`QALink.init(config)`** | Inicializa el SDK. Conecta a QALink, intercepta `fetch`, consola y runtime. Requiere `apiKey` y `appVersion`. |
112
- | **`QALink.interceptAxios(axiosInstance)`** | Registra una instancia de Axios para capturar sus requests/respuestas. Llamar después de `init()`. |
113
- | **`QALink.setScreen(screenName)`** | Indica la pantalla actual. Genera un breadcrumb `NAVIGATE → screenName`. |
114
- | **`QALink.addBreadcrumb(action, data?)`** | Envía un breadcrumb (acción + datos opcionales) a la línea de tiempo del dashboard. |
115
- | **`QALink.logRequest(options)`** | Registra manualmente una petición de red (método, URL, status, payloads, duración). Útil si no usas fetch ni Axios. |
116
- | **`QALink.getDeviceId()`** | Devuelve el `deviceId` que usa el SDK en esta sesión. |
117
- | **`QALink.getStatus()`** | Devuelve el estado de la conexión WebSocket (p. ej. `'connected'`, `'not initialized'`). |
118
- | **`QALink.destroy()`** | Desconecta, elimina todos los interceptores y deja el SDK no inicializado. |
214
+ ### Custom Context
215
+ Datos custom que se adjuntan a todos los eventos:
119
216
 
120
- Además se exporta la función de utilidad **`getSourceLabel`** (para clasificación de fuentes de error) y todos los **tipos** TypeScript (`QALinkConfig`, `LogRequestOptions`, `QALinkEvent`, etc.).
217
+ ```typescript
218
+ QALink.setCustomContext({
219
+ experimentVariant: 'new_ui_v2',
220
+ featureFlags: {
221
+ darkMode: true,
222
+ newCheckout: false
223
+ },
224
+ build: 'staging-142'
225
+ });
226
+ ```
121
227
 
122
228
  ---
123
229
 
124
- ## Configuración (`QALinkConfig`)
125
-
126
- Opciones que acepta `QALink.init(config)`:
127
-
128
- | Campo | Tipo | Requerido | Descripción |
129
- |-------|------|-----------|-------------|
130
- | **`apiKey`** | `string` | ✅ | API key del proyecto (formato: `qlk_` + 64 caracteres hex). |
131
- | **`appVersion`** | `string` | ✅ | Versión de la app (p. ej. `'1.2.3'`). |
132
- | **`enabled`** | `boolean` | No | Activa/desactiva el SDK. Por defecto `true`. |
133
- | **`environment`** | `'dev' \| 'prod'` | No | Entorno. Por defecto `'dev'`. |
134
- | **`logNetworkBodies`** | `boolean` | No | Incluir cuerpos de request/response en eventos de red. Por defecto `false`. |
135
- | **`sensitiveHeaders`** | `string[]` | No | Cabeceras a redactar (p. ej. `['Authorization']`). |
136
- | **`sensitiveUrlPatterns`** | `string[]` | No | Patrones de URL a tratar como sensibles (se redactan). |
137
- | **`sensitiveBodyFields`** | `string[]` | No | Campos del body a redactar (p. ej. `['password', 'token']`). |
138
- | **`console`** | `ConsoleConfig` | No | Captura de logs y filtros (ver abajo). |
139
- | **`captureRuntimeErrors`** | `boolean` | No | Capturar pantalla roja, yellow box, etc. Por defecto `true`. |
140
- | **`captureMetroErrors`** | `boolean` | No | Capturar errores de Metro. Por defecto `true`. |
141
- | **`onEvent`** | `(event: QALinkEvent) => void` | No | Callback por cada evento enviado. |
142
- | **`debug`** | `boolean` | No | Logs internos del SDK en consola. Por defecto `false`. |
143
-
144
- **`console`** (`ConsoleConfig`):
145
-
146
- | Campo | Tipo | Descripción |
147
- |-------|------|-------------|
148
- | **`captureLogs`** | `boolean` | Capturar `console.log`. Por defecto `true`. |
149
- | **`captureWarnings`** | `boolean` | Capturar `console.warn`. Por defecto `true`. |
150
- | **`captureErrors`** | `boolean` | Capturar `console.error`. Por defecto `true`. |
151
- | **`ignorePatterns`** | `string[]` | Mensajes que contengan estos patrones no se envían. |
152
- | **`includePatterns`** | `string[]` | Si se define, solo se envían mensajes que coincidan con estos patrones. |
230
+ ## 📸 Captura de Pantalla
231
+
232
+ **Requisito:** `react-native-view-shot`
233
+
234
+ ```typescript
235
+ // Capturar pantalla completa
236
+ await QALink.captureScreen('before_payment');
237
+ await QALink.captureScreen('error_state');
238
+
239
+ // Capturar componente específico
240
+ import { useRef } from 'react';
241
+
242
+ const MyComponent = () => {
243
+ const viewRef = useRef(null);
244
+
245
+ const handleCapture = async () => {
246
+ await QALink.captureRef(viewRef, 'form_error');
247
+ };
248
+
249
+ return (
250
+ <View ref={viewRef}>
251
+ {/* contenido */}
252
+ </View>
253
+ );
254
+ };
255
+ ```
256
+
257
+ **Configuración:**
258
+
259
+ ```typescript
260
+ QALink.init({
261
+ apiKey: 'qlk_xxx',
262
+ appVersion: '1.0.0',
263
+ enableScreenCapture: true, // ← activar
264
+ });
265
+ ```
153
266
 
154
267
  ---
155
268
 
156
- ## Ejemplos
269
+ ## 🔌 Interceptores HTTP
270
+
271
+ ### Fetch (automático)
272
+ El SDK intercepta **todos** los `fetch()` automáticamente. No necesitas hacer nada.
157
273
 
158
- ### Setup básico
274
+ ### Axios
275
+ Si usas Axios, puedes interceptar instancias específicas:
159
276
 
160
- ```tsx
161
- // App.tsx
162
- import { useEffect } from 'react';
163
- import { QALink } from 'react-native-qalink';
277
+ ```typescript
164
278
  import axios from 'axios';
279
+ import QALink from 'react-native-qalink';
165
280
 
166
- export default function App() {
167
- useEffect(() => {
168
- QALink.init({
169
- apiKey: 'qlk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
170
- appVersion: '1.2.3',
171
- captureRuntimeErrors: true,
172
- console: {
173
- captureLogs: true,
174
- captureWarnings: true,
175
- captureErrors: true,
176
- },
177
- debug: __DEV__,
178
- });
179
-
180
- QALink.interceptAxios(axios);
181
- }, []);
182
-
183
- return <RootNavigator />;
184
- }
281
+ const apiClient = axios.create({
282
+ baseURL: 'https://api.example.com'
283
+ });
284
+
285
+ // Interceptar esta instancia de Axios
286
+ QALink.interceptAxios(apiClient);
185
287
  ```
186
288
 
187
- ### Filtrar logs de librerías
289
+ ---
290
+
291
+ ## 🛠️ Configuración completa
292
+
293
+ ```typescript
294
+ import QALink from 'react-native-qalink';
188
295
 
189
- ```tsx
190
296
  QALink.init({
191
- apiKey: 'qlk_...',
297
+ // ── Requerido ──────────────────────────────────
298
+ apiKey: 'qlk_your_64_char_hex_key',
192
299
  appVersion: '1.2.3',
193
- console: {
194
- captureLogs: true,
195
- captureWarnings: true,
196
- captureErrors: true,
197
- ignorePatterns: [
198
- '[react-query]',
199
- '[redux]',
200
- 'Reanimated',
201
- 'Gesture Handler',
202
- '[MMKV]',
203
- ],
300
+
301
+ // ── Básico ─────────────────────────────────────
302
+ enabled: true, // habilitar/deshabilitar SDK
303
+ environment: 'dev', // 'dev' | 'prod'
304
+ debug: true, // logs de debug en consola
305
+
306
+ // ── HTTP Interceptors ──────────────────────────
307
+ httpConfig: {
308
+ captureRequests: true,
309
+ captureResponses: true,
310
+ includeUrls: ['/api/'],
311
+ excludeUrls: ['/health', '/metrics'],
312
+ methods: ['POST', 'PUT', 'DELETE'],
313
+ sanitizeHeaders: ['Authorization'],
314
+ sanitizeRequestBody: ['password', 'token', 'creditCard'],
315
+ sanitizeResponseBody: ['secret', 'apiKey'],
316
+ captureOnlyErrors: false,
317
+ minStatusCode: 200,
318
+ maxBodySize: 5000,
319
+ captureTimings: true,
320
+ slowRequestThreshold: 2000,
204
321
  },
205
- });
206
- ```
207
322
 
208
- ### Solo logs marcados para QA
323
+ // ── Legacy Network (compatibilidad) ────────────
324
+ logNetworkBodies: false,
325
+ sensitiveHeaders: ['Authorization'],
326
+ sensitiveUrlPatterns: ['/auth/', '/login'],
327
+ sensitiveBodyFields: ['password', 'token'],
209
328
 
210
- ```tsx
211
- QALink.init({
212
- apiKey: 'qlk_...',
213
- appVersion: '1.2.3',
329
+ // ── Console ─────────────────────────────────────
214
330
  console: {
215
331
  captureLogs: true,
216
332
  captureWarnings: true,
217
333
  captureErrors: true,
218
- includePatterns: ['[QA]', '[FLOW]', '[ERROR]'],
334
+ ignorePatterns: ['Ignore this'],
335
+ includePatterns: [],
219
336
  },
220
- });
221
-
222
- // En tu código:
223
- console.log('[QA] Usuario llegó al checkout', { userId, cartTotal });
224
- console.log('[FLOW] Pago procesado', { orderId });
225
- ```
226
337
 
227
- ### Múltiples instancias de Axios
338
+ // ── Errors ──────────────────────────────────────
339
+ captureRuntimeErrors: true, // red screen / yellow box
340
+ captureMetroErrors: true, // bundler errors
228
341
 
229
- ```tsx
230
- const apiPrincipal = axios.create({ baseURL: 'https://api.miapp.com/v1' });
231
- const apiPagos = axios.create({ baseURL: 'https://pagos.miapp.com' });
342
+ // ── Screen Capture ──────────────────────────────
343
+ enableScreenCapture: true, // requiere react-native-view-shot
232
344
 
233
- await QALink.init({ apiKey: 'qlk_...', appVersion: '1.2.3' });
234
- QALink.interceptAxios(apiPrincipal);
235
- QALink.interceptAxios(apiPagos);
345
+ // ── Callbacks ───────────────────────────────────
346
+ onEvent: (event) => {
347
+ console.log('Event sent:', event.type);
348
+ }
349
+ });
236
350
  ```
237
351
 
238
- ### React Navigation + pantalla actual
352
+ ---
239
353
 
240
- ```tsx
241
- <NavigationContainer
242
- ref={navigationRef}
243
- onStateChange={() => {
244
- const routeName = navigationRef.current?.getCurrentRoute()?.name ?? '';
245
- QALink.setScreen(routeName);
246
- }}
247
- >
248
- ```
354
+ ## 📊 Tipos de eventos capturados
355
+
356
+ El SDK captura automáticamente:
357
+
358
+ | Tipo | Descripción |
359
+ |------|-------------|
360
+ | `session_start` | Inicio de sesión |
361
+ | `http_request` | Request HTTP (fetch/axios) |
362
+ | `user_log` | Logs con debug/info/warn/error/critical |
363
+ | `custom_event` | Eventos custom con logEvent() |
364
+ | `console_log` | console.log |
365
+ | `console_warn` | console.warn |
366
+ | `console_error` | console.error |
367
+ | `runtime_error` | Red screen / Yellow box |
368
+ | `metro_error` | Errores de Metro Bundler |
369
+ | `js_error` | Errores de JavaScript |
370
+ | `breadcrumb` | Navegación y acciones |
371
+ | `screen_capture` | Screenshots |
249
372
 
250
- ### Breadcrumbs en acciones críticas
373
+ ---
251
374
 
252
- ```tsx
253
- const handleConfirmarCompra = async () => {
254
- QALink.addBreadcrumb('TAP → Confirmar Compra', {
255
- cartId: cart.id,
256
- items: cart.items.length,
257
- total: cart.total,
258
- });
375
+ ## 🔐 Compatibilidad con fetch-blob
259
376
 
260
- console.log('[QA] Iniciando checkout', { cartId: cart.id });
261
- try {
262
- const result = await apiPagos.post('/checkout', { cartId: cart.id });
263
- console.log('[QA] Checkout exitoso', { orderId: result.data.orderId });
264
- } catch (error) {
265
- console.error('[QA] Checkout falló', error);
266
- }
267
- };
268
- ```
377
+ El interceptor de fetch es **100% compatible** con `fetch-blob`:
269
378
 
270
- ### Registrar request manualmente (`logRequest`)
379
+ ```typescript
380
+ import { fetch, Blob } from 'react-native-fetch-blob';
271
381
 
272
- Si usas un cliente que no es fetch ni Axios:
382
+ // QALink detecta automáticamente Blobs y no intenta serializarlos
383
+ const blob = new Blob([...], { type: 'image/jpeg' });
273
384
 
274
- ```tsx
275
- QALink.logRequest({
385
+ await fetch('https://api.example.com/upload', {
276
386
  method: 'POST',
277
- url: 'https://api.example.com/order',
278
- statusCode: 200,
279
- requestPayload: { items: [1, 2, 3] },
280
- responsePayload: { orderId: 'abc123' },
281
- durationMs: 450,
387
+ body: blob
282
388
  });
389
+
390
+ // En el dashboard verás:
391
+ // body: { _type: 'Blob', size: 12345, type: 'image/jpeg' }
283
392
  ```
284
393
 
285
- ### Ver estado de conexión
394
+ ---
286
395
 
287
- ```tsx
288
- if (QALink.getStatus() === 'connected') {
289
- console.log('QALink grabando. Device:', QALink.getDeviceId());
290
- }
291
- ```
396
+ ## 🎯 Casos de uso
292
397
 
293
- ### Build solo para QA (ej. con react-native-config)
294
-
295
- ```tsx
296
- import Config from 'react-native-config';
297
-
298
- if (Config.QALINK_ENABLED === 'true') {
299
- await QALink.init({
300
- apiKey: Config.QALINK_API_KEY,
301
- appVersion: Config.VERSION_NAME ?? '0.0.0',
302
- logNetworkBodies: true,
303
- captureRuntimeErrors: true,
304
- console: {
305
- captureLogs: true,
306
- captureWarnings: true,
307
- captureErrors: true,
308
- ignorePatterns: ['[react-query]'],
309
- },
310
- sensitiveHeaders: ['Authorization'],
311
- sensitiveUrlPatterns: ['/auth/refresh'],
398
+ ### 1. Tracking de flujo de checkout
399
+ ```typescript
400
+ QALink.setUserContext({ userId: user.id, email: user.email });
401
+
402
+ QALink.logEvent('checkout_started', { cartTotal: 299.99 });
403
+ QALink.info('Loading payment methods');
404
+
405
+ // ... usuario selecciona método de pago ...
406
+ QALink.logEvent('payment_method_selected', { method: 'credit_card' });
407
+
408
+ try {
409
+ const response = await processPayment();
410
+ QALink.logEvent('payment_success', {
411
+ orderId: response.orderId,
412
+ amount: 299.99
413
+ });
414
+ } catch (error) {
415
+ QALink.error('Payment failed', {
416
+ reason: error.message,
417
+ code: error.code
312
418
  });
313
- QALink.interceptAxios(axios);
419
+ await QALink.captureScreen('payment_error');
314
420
  }
315
421
  ```
316
422
 
317
- ---
423
+ ### 2. Debugging de API lenta
424
+ ```typescript
425
+ QALink.configureHTTP({
426
+ captureTimings: true,
427
+ slowRequestThreshold: 1500, // 1.5s
428
+ });
429
+
430
+ // Los requests > 1.5s aparecerán marcados en el dashboard
431
+ ```
318
432
 
319
- ## Qué ve el QA en el dashboard
433
+ ### 3. Tracking de experimentos A/B
434
+ ```typescript
435
+ const variant = getExperimentVariant('new_ui');
320
436
 
321
- Cada sesión incluye una línea de tiempo unificada con todos los eventos:
437
+ QALink.setCustomContext({
438
+ experiment: 'new_ui',
439
+ variant: variant, // 'A' or 'B'
440
+ });
322
441
 
323
- | Ícono | Tipo | Fuente |
324
- |-------|-------------------|------------------------|
325
- | ✅ | Request exitoso | fetch / Axios |
326
- | ❌ | Error de red | fetch / Axios |
327
- | 🔴 | Error fatal (RN) | ErrorUtils / LogBox |
328
- | 🟡 | Warning (RN) | LogBox / console |
329
- | 🔵 | Log de consola | console.log |
330
- | 📍 | Breadcrumb | `QALink.addBreadcrumb` |
331
- | 📱 | Pantalla | `QALink.setScreen` |
442
+ QALink.logEvent('experiment_viewed', {
443
+ experiment: 'new_ui',
444
+ variant
445
+ });
446
+ ```
332
447
 
333
448
  ---
334
449
 
335
- ## Tipos exportados
450
+ ## 🚨 Troubleshooting
336
451
 
337
- Puedes importar tipos para TypeScript:
452
+ ### Los logs no aparecen en el dashboard
453
+ - Verifica que `apiKey` sea correcto (formato: `qlk_<64 hex>`)
454
+ - Verifica que `enabled: true`
455
+ - Revisa la consola: `QALink.getStatus()` debe ser `'connected'`
338
456
 
339
- ```ts
340
- import type {
341
- QALinkConfig,
342
- LogRequestOptions,
343
- QALinkEvent,
344
- BreadcrumbEvent,
345
- NetworkLogEvent,
346
- ConsoleConfig,
347
- } from 'react-native-qalink';
348
- ```
457
+ ### Screen capture no funciona
458
+ - Instala: `npm install react-native-view-shot`
459
+ - Habilita: `enableScreenCapture: true` en config
460
+ - iOS: `cd ios && pod install`
461
+
462
+ ### Axios no se intercepta
463
+ - Llama `QALink.interceptAxios(axiosInstance)` **después** de `QALink.init()`
464
+
465
+ ---
466
+
467
+ ## 📖 Recursos
468
+
469
+ - **Dashboard:** https://qalink.airbites.org
470
+ - **Documentación completa:** (próximamente)
471
+ - **GitHub:** (próximamente)
349
472
 
350
473
  ---
351
474
 
352
- ## Licencia
475
+ ## 📄 Licencia
353
476
 
354
477
  MIT
478
+
479
+ ---
480
+
481
+ ## 🤝 Contribuir
482
+
483
+ Pull requests son bienvenidos. Para cambios mayores, abre primero un issue.