ConfiguracionTributariaManager.tsx 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. "use client"
  2. import { useState } from 'react'
  3. import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
  4. import { Button } from "@/components/ui/button"
  5. import { Input } from "@/components/ui/input"
  6. import { Label } from "@/components/ui/label"
  7. import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
  8. import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"
  9. import { Badge } from "@/components/ui/badge"
  10. import { Switch } from "@/components/ui/switch"
  11. import { useConfiguracionesTributarias } from '@/hooks/useConfiguracionesTributarias'
  12. import { Plus, Edit, Trash2, Save, RotateCcw } from 'lucide-react'
  13. export function ConfiguracionTributariaManager() {
  14. const {
  15. configuraciones,
  16. loading,
  17. error,
  18. createConfiguracion,
  19. updateConfiguracion,
  20. deleteConfiguracion,
  21. incrementarSecuencial,
  22. reiniciarSecuencial,
  23. } = useConfiguracionesTributarias()
  24. const [editingConfig, setEditingConfig] = useState<string | null>(null)
  25. const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false)
  26. const [isResetDialogOpen, setIsResetDialogOpen] = useState(false)
  27. const [resetConfigId, setResetConfigId] = useState<string | null>(null)
  28. const [resetSecuencial, setResetSecuencial] = useState('')
  29. const [formData, setFormData] = useState({
  30. ambiente: '1',
  31. tipoEmision: '1',
  32. razonSocial: '',
  33. nombreComercial: '',
  34. ruc: '',
  35. dirMatriz: '',
  36. estab: '001',
  37. ptoEmi: '001',
  38. secuencial: '000000001',
  39. activo: true,
  40. })
  41. const resetForm = () => {
  42. setFormData({
  43. ambiente: '1',
  44. tipoEmision: '1',
  45. razonSocial: '',
  46. nombreComercial: '',
  47. ruc: '',
  48. dirMatriz: '',
  49. estab: '001',
  50. ptoEmi: '001',
  51. secuencial: '000000001',
  52. activo: true,
  53. })
  54. }
  55. const handleCreate = async () => {
  56. try {
  57. await createConfiguracion(formData)
  58. setIsCreateDialogOpen(false)
  59. resetForm()
  60. } catch (error) {
  61. console.error('Error creating configuration:', error)
  62. }
  63. }
  64. const handleUpdate = async (id: string) => {
  65. try {
  66. await updateConfiguracion(id, formData)
  67. setEditingConfig(null)
  68. resetForm()
  69. } catch (error) {
  70. console.error('Error updating configuration:', error)
  71. }
  72. }
  73. const handleDelete = async (id: string) => {
  74. if (confirm('¿Está seguro de que desea eliminar esta configuración?')) {
  75. try {
  76. await deleteConfiguracion(id)
  77. } catch (error) {
  78. console.error('Error deleting configuration:', error)
  79. }
  80. }
  81. }
  82. const handleIncrementSecuencial = async (id: string) => {
  83. try {
  84. await incrementarSecuencial(id)
  85. } catch (error) {
  86. console.error('Error incrementing secuencial:', error)
  87. }
  88. }
  89. const handleResetSecuencial = (id: string, currentSecuencial: string) => {
  90. setResetConfigId(id)
  91. setResetSecuencial(currentSecuencial)
  92. setIsResetDialogOpen(true)
  93. }
  94. const confirmResetSecuencial = async () => {
  95. if (!resetConfigId || !resetSecuencial) return
  96. try {
  97. await reiniciarSecuencial(resetConfigId, resetSecuencial)
  98. setIsResetDialogOpen(false)
  99. setResetConfigId(null)
  100. setResetSecuencial('')
  101. } catch (error) {
  102. console.error('Error resetting secuencial:', error)
  103. }
  104. }
  105. const cancelReset = () => {
  106. setIsResetDialogOpen(false)
  107. setResetConfigId(null)
  108. setResetSecuencial('')
  109. }
  110. const startEdit = (config: any) => {
  111. setEditingConfig(config.id)
  112. setFormData({
  113. ambiente: config.ambiente,
  114. tipoEmision: config.tipoEmision,
  115. razonSocial: config.razonSocial,
  116. nombreComercial: config.nombreComercial,
  117. ruc: config.ruc,
  118. dirMatriz: config.dirMatriz,
  119. estab: config.estab,
  120. ptoEmi: config.ptoEmi,
  121. secuencial: config.secuencial,
  122. activo: config.activo,
  123. })
  124. }
  125. const cancelEdit = () => {
  126. setEditingConfig(null)
  127. resetForm()
  128. }
  129. if (loading && configuraciones.length === 0) {
  130. return <div className="flex justify-center items-center h-64">Cargando...</div>
  131. }
  132. if (error) {
  133. return <div className="text-red-500 text-center p-4">Error: {error}</div>
  134. }
  135. return (
  136. <div className="space-y-6">
  137. <div className="flex justify-between items-center">
  138. <div>
  139. <h2 className="text-2xl font-bold">Configuraciones Tributarias</h2>
  140. <p className="text-gray-600">Gestiona la información tributaria de tu empresa</p>
  141. </div>
  142. <Dialog open={isCreateDialogOpen} onOpenChange={setIsCreateDialogOpen}>
  143. <DialogTrigger asChild>
  144. <Button onClick={() => { resetForm(); setIsCreateDialogOpen(true); }}>
  145. <Plus className="w-4 h-4 mr-2" />
  146. Nueva Configuración
  147. </Button>
  148. </DialogTrigger>
  149. <DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
  150. <DialogHeader>
  151. <DialogTitle>Nueva Configuración Tributaria</DialogTitle>
  152. <DialogDescription>
  153. Ingresa los datos de la configuración tributaria
  154. </DialogDescription>
  155. </DialogHeader>
  156. <ConfiguracionForm
  157. formData={formData}
  158. setFormData={setFormData}
  159. onSave={handleCreate}
  160. onCancel={() => setIsCreateDialogOpen(false)}
  161. />
  162. </DialogContent>
  163. </Dialog>
  164. </div>
  165. <div className="grid gap-4">
  166. {configuraciones.map((config) => (
  167. <Card key={config.id}>
  168. <CardHeader>
  169. <div className="flex justify-between items-start">
  170. <div>
  171. <CardTitle className="flex items-center gap-2">
  172. {config.razonSocial}
  173. <Badge variant={config.activo ? "default" : "secondary"}>
  174. {config.activo ? "Activa" : "Inactiva"}
  175. </Badge>
  176. </CardTitle>
  177. <CardDescription>
  178. RUC: {config.ruc} | {config.nombreComercial}
  179. </CardDescription>
  180. </div>
  181. <div className="flex gap-2">
  182. {editingConfig === config.id ? (
  183. <>
  184. <Button
  185. size="sm"
  186. onClick={() => handleUpdate(config.id)}
  187. disabled={loading}
  188. >
  189. <Save className="w-4 h-4" />
  190. </Button>
  191. <Button
  192. size="sm"
  193. variant="outline"
  194. onClick={cancelEdit}
  195. disabled={loading}
  196. >
  197. Cancelar
  198. </Button>
  199. </>
  200. ) : (
  201. <>
  202. <Button
  203. size="sm"
  204. variant="outline"
  205. onClick={() => startEdit(config)}
  206. >
  207. <Edit className="w-4 h-4" />
  208. </Button>
  209. <Button
  210. size="sm"
  211. variant="outline"
  212. onClick={() => handleIncrementSecuencial(config.id)}
  213. disabled={loading}
  214. >
  215. + Secuencial
  216. </Button>
  217. <Button
  218. size="sm"
  219. variant="outline"
  220. onClick={() => handleResetSecuencial(config.id, config.secuencial)}
  221. disabled={loading}
  222. >
  223. <RotateCcw className="w-4 h-4" />
  224. </Button>
  225. <Button
  226. size="sm"
  227. variant="destructive"
  228. onClick={() => handleDelete(config.id)}
  229. disabled={loading}
  230. >
  231. <Trash2 className="w-4 h-4" />
  232. </Button>
  233. </>
  234. )}
  235. </div>
  236. </div>
  237. </CardHeader>
  238. <CardContent>
  239. {editingConfig === config.id ? (
  240. <ConfiguracionForm
  241. formData={formData}
  242. setFormData={setFormData}
  243. onSave={() => handleUpdate(config.id)}
  244. onCancel={cancelEdit}
  245. isEdit
  246. />
  247. ) : (
  248. <div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
  249. <div>
  250. <Label className="text-gray-600">Ambiente</Label>
  251. <p>{config.ambiente === '1' ? 'Pruebas' : 'Producción'}</p>
  252. </div>
  253. <div>
  254. <Label className="text-gray-600">Establecimiento</Label>
  255. <p>{config.estab}</p>
  256. </div>
  257. <div>
  258. <Label className="text-gray-600">Punto Emisión</Label>
  259. <p>{config.ptoEmi}</p>
  260. </div>
  261. <div>
  262. <Label className="text-gray-600">Secuencial</Label>
  263. <p>{config.secuencial}</p>
  264. </div>
  265. <div className="col-span-2 md:col-span-4">
  266. <Label className="text-gray-600">Dirección Matriz</Label>
  267. <p>{config.dirMatriz}</p>
  268. </div>
  269. </div>
  270. )}
  271. </CardContent>
  272. </Card>
  273. ))}
  274. {configuraciones.length === 0 && !loading && (
  275. <Card>
  276. <CardContent className="text-center py-8">
  277. <p className="text-gray-500">No hay configuraciones tributarias registradas</p>
  278. <Button
  279. className="mt-4"
  280. onClick={() => setIsCreateDialogOpen(true)}
  281. >
  282. <Plus className="w-4 h-4 mr-2" />
  283. Crear Primera Configuración
  284. </Button>
  285. </CardContent>
  286. </Card>
  287. )}
  288. </div>
  289. {/* Diálogo para reiniciar secuencial */}
  290. <Dialog open={isResetDialogOpen} onOpenChange={setIsResetDialogOpen}>
  291. <DialogContent>
  292. <DialogHeader>
  293. <DialogTitle>Reiniciar Secuencial</DialogTitle>
  294. <DialogDescription>
  295. Ingresa el nuevo número de secuencial para esta configuración.
  296. </DialogDescription>
  297. </DialogHeader>
  298. <div className="space-y-4">
  299. <div className="space-y-2">
  300. <Label htmlFor="resetSecuencial">Nuevo Secuencial</Label>
  301. <Input
  302. id="resetSecuencial"
  303. value={resetSecuencial}
  304. onChange={(e) => setResetSecuencial(e.target.value)}
  305. placeholder="000000001"
  306. maxLength={9}
  307. />
  308. <p className="text-sm text-gray-500">
  309. Ingresa un número entre 0 y 999999999. El sistema lo formateará automáticamente.
  310. </p>
  311. </div>
  312. <div className="flex justify-end gap-2">
  313. <Button variant="outline" onClick={cancelReset}>
  314. Cancelar
  315. </Button>
  316. <Button
  317. onClick={confirmResetSecuencial}
  318. disabled={!resetSecuencial || loading}
  319. >
  320. Reiniciar
  321. </Button>
  322. </div>
  323. </div>
  324. </DialogContent>
  325. </Dialog>
  326. </div>
  327. )
  328. }
  329. interface ConfiguracionFormProps {
  330. formData: any
  331. setFormData: (data: any) => void
  332. onSave: () => void
  333. onCancel: () => void
  334. isEdit?: boolean
  335. }
  336. function ConfiguracionForm({ formData, setFormData, onSave, onCancel, isEdit = false }: ConfiguracionFormProps) {
  337. return (
  338. <div className="space-y-4">
  339. <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
  340. <div className="space-y-2">
  341. <Label htmlFor="ambiente">Ambiente</Label>
  342. <Select value={formData.ambiente} onValueChange={(value) => setFormData({ ...formData, ambiente: value })}>
  343. <SelectTrigger>
  344. <SelectValue />
  345. </SelectTrigger>
  346. <SelectContent>
  347. <SelectItem value="1">1 - Pruebas</SelectItem>
  348. <SelectItem value="2">2 - Producción</SelectItem>
  349. </SelectContent>
  350. </Select>
  351. </div>
  352. <div className="space-y-2">
  353. <Label htmlFor="tipoEmision">Tipo Emisión</Label>
  354. <Select value={formData.tipoEmision} onValueChange={(value) => setFormData({ ...formData, tipoEmision: value })}>
  355. <SelectTrigger>
  356. <SelectValue />
  357. </SelectTrigger>
  358. <SelectContent>
  359. <SelectItem value="1">1 - Normal</SelectItem>
  360. </SelectContent>
  361. </Select>
  362. </div>
  363. <div className="space-y-2">
  364. <Label htmlFor="ruc">RUC *</Label>
  365. <Input
  366. id="ruc"
  367. value={formData.ruc}
  368. onChange={(e) => setFormData({ ...formData, ruc: e.target.value })}
  369. placeholder="13 dígitos"
  370. maxLength={13}
  371. />
  372. </div>
  373. <div className="space-y-2">
  374. <Label htmlFor="razonSocial">Razón Social *</Label>
  375. <Input
  376. id="razonSocial"
  377. value={formData.razonSocial}
  378. onChange={(e) => setFormData({ ...formData, razonSocial: e.target.value })}
  379. placeholder="Empresa S.A."
  380. />
  381. </div>
  382. <div className="space-y-2">
  383. <Label htmlFor="nombreComercial">Nombre Comercial *</Label>
  384. <Input
  385. id="nombreComercial"
  386. value={formData.nombreComercial}
  387. onChange={(e) => setFormData({ ...formData, nombreComercial: e.target.value })}
  388. placeholder="Nombre Comercial"
  389. />
  390. </div>
  391. <div className="space-y-2">
  392. <Label htmlFor="estab">Establecimiento</Label>
  393. <Input
  394. id="estab"
  395. value={formData.estab}
  396. onChange={(e) => setFormData({ ...formData, estab: e.target.value })}
  397. placeholder="001"
  398. maxLength={3}
  399. />
  400. </div>
  401. <div className="space-y-2">
  402. <Label htmlFor="ptoEmi">Punto Emisión</Label>
  403. <Input
  404. id="ptoEmi"
  405. value={formData.ptoEmi}
  406. onChange={(e) => setFormData({ ...formData, ptoEmi: e.target.value })}
  407. placeholder="001"
  408. maxLength={3}
  409. />
  410. </div>
  411. <div className="space-y-2">
  412. <Label htmlFor="secuencial">Secuencial</Label>
  413. <Input
  414. id="secuencial"
  415. value={formData.secuencial}
  416. onChange={(e) => setFormData({ ...formData, secuencial: e.target.value })}
  417. placeholder="000000001"
  418. maxLength={9}
  419. />
  420. </div>
  421. <div className="space-y-2 md:col-span-2">
  422. <Label htmlFor="dirMatriz">Dirección Matriz *</Label>
  423. <Input
  424. id="dirMatriz"
  425. value={formData.dirMatriz}
  426. onChange={(e) => setFormData({ ...formData, dirMatriz: e.target.value })}
  427. placeholder="Av. Principal 123 y Secundaria"
  428. />
  429. </div>
  430. {isEdit && (
  431. <div className="flex items-center space-x-2">
  432. <Switch
  433. id="activo"
  434. checked={formData.activo}
  435. onCheckedChange={(checked) => setFormData({ ...formData, activo: checked })}
  436. />
  437. <Label htmlFor="activo">Activa</Label>
  438. </div>
  439. )}
  440. </div>
  441. <div className="flex justify-end gap-2 pt-4">
  442. <Button variant="outline" onClick={onCancel}>
  443. Cancelar
  444. </Button>
  445. <Button onClick={onSave}>
  446. {isEdit ? 'Actualizar' : 'Guardar'}
  447. </Button>
  448. </div>
  449. </div>
  450. )
  451. }