{"_id":"@alejo-dev/xavi-providers","_rev":"3-59a21e9f53898bfdba73154ce4fc8132","name":"@alejo-dev/xavi-providers","dist-tags":{"latest":"1.0.2"},"versions":{"1.0.0":{"name":"@alejo-dev/xavi-providers","version":"1.0.0","keywords":[],"author":"","license":"ISC","_id":"@alejo-dev/xavi-providers@1.0.0","maintainers":[{"name":"alejandro.devop","email":"alejandro.devop@gmail.com"}],"dist":{"shasum":"93310ca86f806b1b7f8ea0f192afc0dd844254eb","tarball":"https://registry.npmjs.org/@alejo-dev/xavi-providers/-/xavi-providers-1.0.0.tgz","fileCount":55,"integrity":"sha512-P2eNQEtrtdRd9ArxTx5YtStibNsfXY+3jOh44ZHYZEEWy5XP97Vdi/16sy9B/WGOuNk4GUtY0lS9+Q5aYei8GA==","signatures":[{"sig":"MEYCIQCTF4TLy3pN96/NH1rHj0zeVnKFOuDmMp/Bw28UQ25WqQIhAOnOkJhFHOJ43SYe55va0yMyVRGYo8bpeUEHyUcHxHTN","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":22285},"main":"dist/index.js","types":"dist/index.d.ts","gitHead":"ba2fa5f1ca874d84ed710473d1ce32d3592f7dfe","scripts":{"dev":"tsc --watch","build":"tsc"},"_npmUser":{"name":"alejandro.devop","actor":{"name":"alejandro.devop","type":"user","email":"alejandro.devop@gmail.com"},"email":"alejandro.devop@gmail.com"},"_npmVersion":"10.2.4","directories":{},"_nodeVersion":"20.11.0","dependencies":{"axios":"^1.9.0","moment":"^2.30.1","@babel/core":"^7.25.2","@babel/runtime":"^7.25.0","@babel/preset-env":"^7.25.3","@tanstack/react-query":"^5.75.5","react-native-reanimated":"3.17.5","@alejo-dev/xavi-theme-provider":"*","@tanstack/react-query-persist-client":"^5.77.1","@react-native-async-storage/async-storage":"^2.1.2"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.8.3","@types/axios":"^0.14.4","@types/react":"^19.1.8","@types/react-query":"^1.2.9","@types/react-native":"^0.73.0"},"peerDependencies":{"axios":"^1.9.0","react":">=19.0.0","moment":"^2.30.1","react-native":">=0.79.2","@tanstack/react-query":"^5.75.5","react-native-reanimated":"3.17.5","@alejo-dev/xavi-theme-provider":"*","@tanstack/react-query-persist-client":"^5.77.1","@react-native-async-storage/async-storage":"^2.1.2"},"_npmOperationalInternal":{"tmp":"tmp/xavi-providers_1.0.0_1751903400289_0.700565461948232","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@alejo-dev/xavi-providers","version":"1.0.1","keywords":[],"author":"","license":"ISC","_id":"@alejo-dev/xavi-providers@1.0.1","maintainers":[{"name":"alejandro.devop","email":"alejandro.devop@gmail.com"}],"dist":{"shasum":"70d8bf8925004a3129523942047e6814df951304","tarball":"https://registry.npmjs.org/@alejo-dev/xavi-providers/-/xavi-providers-1.0.1.tgz","fileCount":55,"integrity":"sha512-joWuUHKWrRcDajy2UHkXX6MF0p29hLRgqqAKtlhgh1sKT+WuIa51Vh4T9gkVXr+O5VeemCpYvPPyeXyVLRC/wg==","signatures":[{"sig":"MEQCIFKTucp9MZaF4nfW0nal0rBDDMLDrIC4BfGEULPN2sJfAiABjchCB+yBfQME0/cqrnLVgrbDNddtpt/svGii1jRwmA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":22578},"main":"dist/index.js","types":"dist/index.d.ts","gitHead":"79e3f4bcec775e93f13ee74f9a5b0f47cfbc0c59","scripts":{"dev":"tsc --watch","build":"tsc"},"_npmUser":{"name":"alejandro.devop","actor":{"name":"alejandro.devop","type":"user","email":"alejandro.devop@gmail.com"},"email":"alejandro.devop@gmail.com"},"_npmVersion":"10.2.4","directories":{},"_nodeVersion":"20.11.0","dependencies":{"axios":"^1.9.0","moment":"^2.30.1","@babel/core":"^7.25.2","@babel/runtime":"^7.25.0","@babel/preset-env":"^7.25.3","@tanstack/react-query":"^5.75.5","react-native-reanimated":"3.18.0","@alejo-dev/xavi-theme-provider":"*","@tanstack/react-query-persist-client":"^5.77.1","@react-native-async-storage/async-storage":"^2.1.2"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.8.3","@types/axios":"^0.14.4","@types/react":"^19.1.8","@types/react-query":"^1.2.9","@types/react-native":"^0.73.0"},"peerDependencies":{"axios":"^1.9.0","react":">=19.0.0","moment":"^2.30.1","react-native":">=0.79.2","@tanstack/react-query":"^5.75.5","react-native-reanimated":"3.18.0","@alejo-dev/xavi-theme-provider":"*","@tanstack/react-query-persist-client":"^5.77.1","@react-native-async-storage/async-storage":"^2.1.2"},"_npmOperationalInternal":{"tmp":"tmp/xavi-providers_1.0.1_1751924403605_0.9693844425735465","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"name":"@alejo-dev/xavi-providers","version":"1.0.2","description":"Un proveedor de API cliente modular y fuertemente tipado para React Native con soporte completo para React Query v5, persistencia automática, detección de conectividad y modo offline.","main":"dist/index.js","types":"dist/index.d.ts","scripts":{"build":"tsc","dev":"tsc --watch","test":"jest","test:watch":"jest --watch","test:coverage":"jest --coverage"},"keywords":[],"author":"","license":"ISC","dependencies":{"@alejo-dev/xavi-theme-provider":"*","@babel/core":"^7.25.2","@babel/preset-env":"^7.25.3","@babel/runtime":"^7.25.0","@react-native-async-storage/async-storage":"^2.1.2","@tanstack/react-query":"^5.75.5","@tanstack/react-query-persist-client":"^5.77.1","axios":"^1.13.5","moment":"^2.30.1","react-native-reanimated":"3.18.0"},"peerDependencies":{"@alejo-dev/xavi-theme-provider":"*","@react-native-async-storage/async-storage":"^2.1.2","@tanstack/react-query":"^5.75.5","@tanstack/react-query-persist-client":"^5.77.1","axios":"^1.13.5","moment":"^2.30.1","react":">=19.0.0","react-native":">=0.79.2","react-native-reanimated":"3.18.0"},"devDependencies":{"@testing-library/dom":"^10.4.1","@testing-library/react":"^16","@types/axios":"^0.14.4","@types/jest":"29","@types/react":"^19.2.14","@types/react-native":"^0.73.0","@types/react-query":"^1.2.9","axios":"^1.13.5","jest":"29","jest-environment-jsdom":"^30.3.0","react-dom":"19.0.0","react-native":"^0.84.0","ts-jest":"29","typescript":"^5.8.3"},"_id":"@alejo-dev/xavi-providers@1.0.2","gitHead":"d350e4e519f2daacdc5bb4dc9bb9d2ef7e9aecb4","_nodeVersion":"22.14.0","_npmVersion":"10.9.2","dist":{"integrity":"sha512-Q4lzJoyThDlharlDxzj7amy8eq7PV/BTolA8MbBeahgYmCmPfTRhiIClJfFJ9/l8MR/rxgcY8azdpI5VsNM5sg==","shasum":"82a6d24f73cb7d138cfb587564647757e3093c6d","tarball":"https://registry.npmjs.org/@alejo-dev/xavi-providers/-/xavi-providers-1.0.2.tgz","fileCount":130,"unpackedSize":124443,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCICn6CzgithEOAij8HyMF0lOfLgzjfU1pdIItJeIq70TYAiEAmh3k/rpwFVZV67i79nCFkQbowqZwcPZijZM+Hvm53Qg="}]},"_npmUser":{"name":"alejandro.devop","email":"alejandro.devop@gmail.com"},"directories":{},"maintainers":[{"name":"alejandro.devop","email":"alejandro.devop@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/xavi-providers_1.0.2_1774462911501_0.8534083543597444"},"_hasShrinkwrap":false}},"time":{"created":"2025-07-07T15:50:00.147Z","modified":"2026-03-25T18:21:51.796Z","1.0.0":"2025-07-07T15:50:00.450Z","1.0.1":"2025-07-07T21:40:03.781Z","1.0.2":"2026-03-25T18:21:51.649Z"},"license":"ISC","keywords":[],"maintainers":[{"name":"alejandro.devop","email":"alejandro.devop@gmail.com"}],"readme":"# @alejo-dev/xavi-providers\n\nUn proveedor de API cliente modular y fuertemente tipado para React Native con soporte completo para React Query v5, persistencia automática, detección de conectividad y modo offline.\n\n## 🚀 Características\n\n- ✅ **React Query v5** con persistencia AsyncStorage\n- ✅ **Hooks REST completos** (GET, POST, PUT, PATCH, DELETE, UPLOAD)\n- ✅ **Hooks GraphQL** (Query, Mutation, Infinite Query)\n- ✅ **Detección automática de conectividad** y estado del servidor\n- ✅ **Modo offline manual** para debugging\n- ✅ **Ping/health checks** configurables\n- ✅ **Manejo de errores normalizado**\n- ✅ **TypeScript fuerte** con generics\n- ✅ **Compatible con React Native**\n\n## 📦 Instalación\n\n```bash\nnpm install @alejo-dev/xavi-providers\n# o\nyarn add @alejo-dev/xavi-providers\n```\n\n### Dependencias requeridas\n\nAsegúrate de tener instaladas estas dependencias peer:\n\n```json\n{\n  \"@react-native-async-storage/async-storage\": \"^2.1.2\",\n  \"@tanstack/react-query\": \"^5.75.5\",\n  \"@tanstack/react-query-persist-client\": \"^5.77.1\",\n  \"axios\": \"^1.9.0\",\n  \"react\": \">=19.0.0\",\n  \"react-native\": \">=0.79.2\"\n}\n```\n\n## 🏗️ Configuración Básica\n\n### 1. Configura el QueryClient\n\n```tsx\nimport { QueryClient } from '@tanstack/react-query';\nimport { createAsyncStoragePersistor } from '@tanstack/react-query-persist-client';\nimport AsyncStorage from '@react-native-async-storage/async-storage';\n\nconst queryClient = new QueryClient({\n  defaultOptions: {\n    queries: {\n      staleTime: 1000 * 60 * 5, // 5 minutos\n      gcTime: 1000 * 60 * 60, // 1 hora\n    },\n  },\n});\n\nconst asyncStoragePersistor = createAsyncStoragePersistor({\n  storage: AsyncStorage,\n  key: 'REACT_QUERY_CACHE',\n});\n```\n\n### 2. Envuelve tu App con el Provider\n\n```tsx\nimport React from 'react';\nimport { ApiClientProvider } from '@alejo-dev/xavi-providers';\n\nexport default function App() {\n  return (\n    <ApiClientProvider\n      queryClient={queryClient}\n      config={{\n        pingEndpoint: '/api/health',\n        enableServerHealthCheck: true,\n        enableInitialPing: true,\n      }}\n    >\n      {/* Tu aplicación aquí */}\n    </ApiClientProvider>\n  );\n}\n```\n\n## 🔧 Configuración Avanzada\n\n```tsx\nconst config = {\n  // Endpoint para health checks\n  pingEndpoint: '/api/health',\n  pingMethod: 'GET',\n  pingHeaders: { Authorization: 'Bearer token' },\n  pingTimeoutMs: 5000,\n\n  // Reintentos y offline\n  offlineRetryIntervalMs: 30000,\n  maxConsecutiveFailuresBeforeOffline: 3,\n  offlineStatusCodes: [502, 503, 504],\n\n  // Validación de respuesta de ping\n  pingResponseValidator: (response) => response.status === 200,\n\n  // Modo offline manual\n  manualOfflineStrategy: 'block-network', // o 'serve-cache-and-block-network'\n  enableDebugOfflineControls: __DEV__,\n\n  // --- Health check mode ---\n  // 'always' (default): health check inicial y periódicos\n  // 'on-failure': solo hace health check cuando una request falla\n  healthCheckMode: 'always', // o 'on-failure'\n};\n```\n\n## 📡 Uso de Hooks REST\n\n### GET Query\n\n```tsx\nimport { useGetQuery } from '@alejo-dev/xavi-providers';\n\ninterface User {\n  id: number;\n  name: string;\n  email: string;\n}\n\nfunction UserProfile({ userId }: { userId: number }) {\n  const { data, isLoading, error } = useGetQuery<User>({\n    endpoint: `/api/users/${userId}`,\n    queryKey: ['user', userId],\n    enabled: !!userId,\n  });\n\n  if (isLoading) return <Text>Loading...</Text>;\n  if (error) return <Text>Error: {error.message}</Text>;\n\n  return (\n    <View>\n      <Text>{data?.name}</Text>\n      <Text>{data?.email}</Text>\n    </View>\n  );\n}\n```\n\n### Infinite Query\n\n```tsx\nimport { useInfiniteGetQuery } from '@alejo-dev/xavi-providers';\n\nfunction PostsList() {\n  const { data, fetchNextPage, hasNextPage, isLoading, isFetchingNextPage } = useInfiniteGetQuery({\n    endpoint: '/api/posts',\n    queryKey: ['posts'],\n    getNextPageParam: (lastPage) => lastPage.nextCursor,\n  });\n\n  const posts = data?.pages.flatMap((page) => page.posts) ?? [];\n\n  return (\n    <FlatList\n      data={posts}\n      onEndReached={() => hasNextPage && fetchNextPage()}\n      // ... otros props\n    />\n  );\n}\n```\n\n### Mutations (POST, PUT, PATCH, DELETE)\n\n```tsx\nimport { usePostMutation, usePutMutation, useDeleteMutation } from '@alejo-dev/xavi-providers';\n\nfunction UserForm() {\n  const createUser = usePostMutation({\n    endpoint: '/api/users',\n    onSuccess: (data) => {\n      console.log('Usuario creado:', data);\n      // Invalidar queries relacionadas\n      queryClient.invalidateQueries({ queryKey: ['users'] });\n    },\n  });\n\n  const updateUser = usePutMutation<UpdateInput>({\n    // El endpoint puede ser una función de las variables para URLs dinámicas\n    endpoint: (variables) => `/api/users/${variables.id}`,\n    onSuccess: () => {\n      // Actualizar cache optimísticamente\n      queryClient.setQueryData(['user', userId], updatedUser);\n    },\n  });\n\n  const deleteUser = useDeleteMutation<DeleteInput>({\n    endpoint: (variables) => `/api/users/${variables.id}`,\n    onSuccess: () => {\n      queryClient.removeQueries({ queryKey: ['user', userId] });\n    },\n  });\n\n  const handleSubmit = (userData) => {\n    createUser.mutate(userData);\n  };\n\n  return (\n    <Button\n      title=\"Crear Usuario\"\n      onPress={() => handleSubmit(formData)}\n      disabled={createUser.isPending}\n    />\n  );\n}\n```\n\n### File Upload\n\n```tsx\nimport { useUploadMutation } from '@alejo-dev/xavi-providers';\n\nfunction ImageUploader() {\n  const uploadImage = useUploadMutation({\n    endpoint: '/api/upload',\n    onSuccess: (data) => {\n      console.log('Imagen subida:', data.url);\n    },\n  });\n\n  const handleUpload = async (imageUri) => {\n    const formData = new FormData();\n    formData.append('image', {\n      uri: imageUri,\n      type: 'image/jpeg',\n      name: 'photo.jpg',\n    });\n\n    uploadImage.mutate(formData);\n  };\n\n  return <Button title=\"Subir Imagen\" onPress={() => handleUpload(imageUri)} />;\n}\n```\n\n## 🌐 Uso de Hooks GraphQL\n\n### GraphQL Query\n\n```tsx\nimport { useGQLQuery } from '@alejo-dev/xavi-providers';\n\nconst GET_USER = `\n  query GetUser($id: ID!) {\n    user(id: $id) {\n      id\n      name\n      email\n    }\n  }\n`;\n\nfunction UserProfile({ userId }: { userId: string }) {\n  const { data, isLoading } = useGQLQuery({\n    document: GET_USER,\n    variables: { id: userId },\n    queryKey: ['user', userId],\n  });\n\n  return <Text>{data?.user?.name}</Text>;\n}\n```\n\n### GraphQL Mutation\n\n```tsx\nimport { useGQLMutation } from '@alejo-dev/xavi-providers';\n\nconst CREATE_USER = `\n  mutation CreateUser($input: CreateUserInput!) {\n    createUser(input: $input) {\n      id\n      name\n      email\n    }\n  }\n`;\n\nfunction CreateUserForm() {\n  const createUser = useGQLMutation({\n    document: CREATE_USER,\n    onSuccess: (data) => {\n      console.log('Usuario creado:', data.createUser);\n    },\n  });\n\n  return <Button title=\"Crear Usuario\" onPress={() => createUser.mutate({ input: formData })} />;\n}\n```\n\n## 📶 Gestión de Estado Offline\n\n### Estado del Servidor\n\n```tsx\nimport { useApiServerStatus } from '@alejo-dev/xavi-providers';\n\nfunction ConnectionStatus() {\n  const {\n    status,\n    isServerReachable,\n    isCheckingConnection,\n    offlineReason,\n    consecutiveFailures,\n    lastOnlineAt,\n    lastOfflineAt,\n  } = useApiServerStatus();\n\n  return (\n    <View>\n      <Text>Estado: {status}</Text>\n      <Text>Conectado: {isServerReachable ? 'Sí' : 'No'}</Text>\n      <Text>Razón offline: {offlineReason}</Text>\n      <Text>Fallos consecutivos: {consecutiveFailures}</Text>\n    </View>\n  );\n}\n```\n\n### Modo Offline Manual (Debugging)\n\n```tsx\nimport { useApiOfflineMode } from '@alejo-dev/xavi-providers';\n\nfunction DebugControls() {\n  const {\n    isManualOfflineEnabled,\n    isEffectiveOfflineMode,\n    enableManualOfflineMode,\n    disableManualOfflineMode,\n    toggleManualOfflineMode,\n  } = useApiOfflineMode();\n\n  return (\n    <View>\n      <Text>Offline Manual: {isManualOfflineEnabled ? 'Activado' : 'Desactivado'}</Text>\n      <Text>Offline Efectivo: {isEffectiveOfflineMode ? 'Sí' : 'No'}</Text>\n\n      <Button\n        title={isManualOfflineEnabled ? 'Desactivar Offline' : 'Activar Offline'}\n        onPress={toggleManualOfflineMode}\n      />\n    </View>\n  );\n}\n```\n\n### Acciones del Servidor\n\n```tsx\nimport { useApiServerStatus } from '@alejo-dev/xavi-providers';\n\nfunction ServerActions() {\n  const { checkServerConnection, markOnline, markOffline } = useApiServerStatus();\n\n  return (\n    <View>\n      <Button title=\"Verificar Conexión\" onPress={checkServerConnection} />\n      <Button title=\"Marcar Online\" onPress={() => markOnline()} />\n      <Button title=\"Marcar Offline\" onPress={() => markOffline('manual-debug')} />\n    </View>\n  );\n}\n```\n\n## 🛠️ Utilidades\n\n### Crear Query Keys\n\n```tsx\nimport { createQueryKeys } from '@alejo-dev/xavi-providers';\n\nexport const userKeys = createQueryKeys('users');\n// {\n//   all: () => ['users'],\n//   lists: () => ['users', 'list'],\n//   list: (filters) => ['users', 'list', 'serialized-filters'],\n//   details: () => ['users', 'detail'],\n//   detail: (id) => ['users', 'detail', id],\n//   infinite: () => ['users', 'infinite'],\n//   infiniteList: (filters) => ['users', 'infinite', 'serialized-filters'],\n// }\n\n// Los filtros se serializan automáticamente para mantener consistencia\nconst filters = { status: 'active', sort: 'name' };\nuserKeys.list(filters); // ['users', 'list', '{\"sort\":\"name\",\"status\":\"active\"}']\n```\n\n### Detección de Errores de Conectividad\n\n```tsx\nimport { isConnectivityError, normalizeRestError } from '@alejo-dev/xavi-providers';\n\ntry {\n  // alguna petición\n} catch (error) {\n  if (isConnectivityError(error)) {\n    console.log('Error de red - activando modo offline');\n  } else {\n    const normalizedError = normalizeRestError(error);\n    console.log('Error de aplicación:', normalizedError.message);\n  }\n}\n```\n\n### Error Offline Manual\n\n```tsx\nimport { ManualOfflineError, isManualOfflineError } from '@alejo-dev/xavi-providers';\n\ntry {\n  // petición que falla en modo offline\n} catch (error) {\n  if (isManualOfflineError(error)) {\n    console.log('Modo offline manual activado');\n  }\n}\n```\n\n## 📋 Estados del Servidor\n\n| Estado     | Descripción                      |\n| ---------- | -------------------------------- |\n| `unknown`  | Estado inicial, sin verificación |\n| `online`   | Servidor reachable               |\n| `checking` | Verificando conectividad         |\n| `offline`  | Servidor no reachable            |\n\n### Razones de Offline\n\n- `none` - Conectado\n- `manual-debug` - Activado manualmente\n- `network-error` - Error de red\n- `ping-failed` - Ping falló\n- `server-unreachable` - Servidor no responde\n\n## 🔄 Ciclo de Vida de Peticiones\n\nTodas las peticiones automáticamente:\n\n1. **En éxito**: Llaman `handleRequestSuccess()` → resetean contador de fallos\n2. **En fallo**: Verifican si es error de conectividad → incrementan contador\n3. **Después de N fallos**: Disparan verificación de ping\n4. **Ping falla**: Marcan como offline\n5. **Ping reintenta**: Cada intervalo configurado\n\n## ⚡ Mejores Prácticas\n\n### 1. Query Keys Consistentes\n\n```tsx\n// ✅ Bueno\nconst userKeys = createQueryKeys('users');\nconst { data } = useGetQuery({ queryKey: userKeys.detail(userId) });\n\n// ❌ Evitar\nconst { data } = useGetQuery({ queryKey: ['user', userId] });\n```\n\n### 2. Invalidación Inteligente\n\n```tsx\nconst createUser = usePostMutation({\n  endpoint: '/api/users',\n  onSuccess: () => {\n    // Invalidar lista de usuarios\n    queryClient.invalidateQueries({ queryKey: userKeys.lists() });\n  },\n});\n```\n\n### 3. Cache Optimista\n\n```tsx\nconst updateUser = usePutMutation({\n  endpoint: `/api/users/${userId}`,\n  onMutate: async (newUser) => {\n    // Cancelar queries salientes\n    await queryClient.cancelQueries({ queryKey: userKeys.detail(userId) });\n\n    // Snapshot del valor anterior\n    const previousUser = queryClient.getQueryData(userKeys.detail(userId));\n\n    // Actualizar cache optimísticamente\n    queryClient.setQueryData(userKeys.detail(userId), newUser);\n\n    return { previousUser };\n  },\n  onError: (err, newUser, context) => {\n    // Revertir en caso de error\n    if (context?.previousUser) {\n      queryClient.setQueryData(userKeys.detail(userId), context.previousUser);\n    }\n  },\n});\n```\n\n### 4. Manejo de Errores\n\n```tsx\nconst { data, error, isError } = useGetQuery({\n  // ...\n});\n\nif (isError) {\n  if (isConnectivityError(error)) {\n    return <OfflineMessage />;\n  } else {\n    return <ErrorMessage error={normalizeRestError(error)} />;\n  }\n}\n```\n\n## 📊 Tipos TypeScript\n\n```tsx\n// Configuración\ntype ApiClientConfig = {\n  pingEndpoint?: string;\n  pingMethod?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n  pingHeaders?: Record<string, string>;\n  pingTimeoutMs?: number;\n  offlineRetryIntervalMs?: number;\n  enableServerHealthCheck?: boolean;\n  enableInitialPing?: boolean;\n  maxConsecutiveFailuresBeforeOffline?: number;\n  offlineStatusCodes?: number[];\n  pingResponseValidator?: (response: any) => boolean;\n  manualOfflineStrategy?: 'block-network' | 'serve-cache-and-block-network';\n  enableDebugOfflineControls?: boolean;\n  healthCheckMode?: 'always' | 'on-failure';\n};\n\n// Estado del servidor\ntype ServerStatus = 'unknown' | 'online' | 'checking' | 'offline';\ntype OfflineReason =\n  | 'none'\n  | 'manual-debug'\n  | 'network-error'\n  | 'ping-failed'\n  | 'server-unreachable';\n\n// Errores\nclass ManualOfflineError extends Error {\n  constructor(message?: string);\n}\nfunction isManualOfflineError(error: any): error is ManualOfflineError;\n```\n\n## 🐛 Debugging\n\n### Estado de React Query DevTools\n\nInstala `@tanstack/react-query-devtools` para inspeccionar el estado del cache:\n\n```tsx\nimport { ReactQueryDevtools } from '@tanstack/react-query-devtools';\n\nexport default function App() {\n  return (\n    <ApiClientProvider>\n      {/* Tu app */}\n      <ReactQueryDevtools initialIsOpen={false} />\n    </ApiClientProvider>\n  );\n}\n```\n\n## 📝 Notas de Migración\n\nSi actualizas desde una versión anterior:\n\n1. **Provider**: El `ApiClientProvider` ahora acepta un prop `config` opcional\n2. **Persistencia**: La configuración de persistencia se mantiene igual\n3. **Hooks**: Todos los hooks existentes siguen funcionando\n4. **Nuevos hooks**: Los nuevos hooks están disponibles pero son opcionales\n\n## 🤝 Contribuir\n\n1. Fork el repositorio\n2. Crea una branch para tu feature (`git checkout -b feature/amazing-feature`)\n3. Commit tus cambios (`git commit -m 'Add amazing feature'`)\n4. Push a la branch (`git push origin feature/amazing-feature`)\n5. Abre un Pull Request\n\n## 📄 Licencia\n\nISC\n","readmeFilename":"README.md","description":"Un proveedor de API cliente modular y fuertemente tipado para React Native con soporte completo para React Query v5, persistencia automática, detección de conectividad y modo offline."}