فهرست منبع

fix accesskey generation

Matthew Trejo 1 ماه پیش
والد
کامیت
151213387a

+ 1 - 1
src/app/factura/page.tsx

@@ -48,7 +48,7 @@ export default function FacturaPage() {
   return (
     <div className="space-y-6">
       {/* Header */}
-      {/* <FacturaHeader /> */}
+      <FacturaHeader />
 
       {/* Info Tributaria */}
       <InfoTributariaForm

+ 2 - 1
src/components/factura/DetalleItemForm.tsx

@@ -173,7 +173,8 @@ export function DetalleItemForm({ item, onChange, onRemove }: DetalleItemFormPro
             </SelectTrigger>
             <SelectContent>
               <SelectItem value="15">15%</SelectItem>
-              <SelectItem value="12">12%</SelectItem>
+              <SelectItem value="13">13%</SelectItem>
+              <SelectItem value="5">5%</SelectItem>
               <SelectItem value="0">0%</SelectItem>
             </SelectContent>
           </Select>

+ 5 - 5
src/components/factura/FacturaHeader.tsx

@@ -1,10 +1,10 @@
 export function FacturaHeader() {
   return (
-    <div className="text-center">
-      <h1 className="text-4xl font-bold tracking-tight">Factura Electrónica SRI Ecuador</h1>
-      <p className="mt-2 text-muted-foreground">
-        Generador de XML para facturación electrónica SRI
-      </p>
+    <div className="flex justify-between items-center">
+      <div>
+        <h2 className="text-2xl font-bold">Creación de Factura</h2>
+        <p className="mt-2 text-muted-foreground">Ingresa los datos para crear un documento XML</p>
+      </div>
     </div>
   )
 }

+ 1 - 12
src/components/factura/InfoTributariaForm.tsx

@@ -27,7 +27,7 @@ export function InfoTributariaForm({ infoTributaria, onChange }: InfoTributariaF
     onChange('estab', config.estab)
     onChange('ptoEmi', config.ptoEmi)
     onChange('secuencial', config.secuencial)
-    // Nota: claveAcceso no se guarda en la base de datos por seguridad
+    // Nota: claveAcceso se genera automáticamente al crear el XML
   }
 
   return (
@@ -126,17 +126,6 @@ export function InfoTributariaForm({ infoTributaria, onChange }: InfoTributariaF
             />
           </div>
           
-          <div className="space-y-2">
-            <Label htmlFor="claveAcceso">Clave de Acceso *</Label>
-            <Input
-              id="claveAcceso"
-              value={infoTributaria.claveAcceso}
-              onChange={(e) => onChange('claveAcceso', e.target.value)}
-              placeholder="49 dígitos"
-              maxLength={49}
-            />
-          </div>
-          
           <div className="space-y-2">
             <Label htmlFor="estab">Establecimiento</Label>
             <Input

+ 1 - 1
src/hooks/factura/useFacturaState.ts

@@ -8,7 +8,7 @@ export function useFacturaState() {
     razonSocial: '',
     nombreComercial: '',
     ruc: '',
-    claveAcceso: '',
+    // claveAcceso se genera automáticamente
     estab: '001',
     ptoEmi: '001',
     secuencial: '000000001',

+ 15 - 9
src/hooks/factura/useXmlGeneration.ts

@@ -1,9 +1,10 @@
 import { toast } from "sonner"
-import { validateRUC, validateClaveAcceso } from "@/utils/factura/validations"
+import { validateRUC } from "@/utils/factura/validations"
+import { generateClaveAcceso } from "@/utils/factura/claveAcceso"
 import type { InfoTributaria, InfoFactura, DetalleItem, TotalImpuesto } from "@/types/factura"
 
 export function useXmlGeneration() {
-  
+
   const generarXml = (
     infoTributaria: InfoTributaria,
     infoFactura: InfoFactura,
@@ -17,11 +18,6 @@ export function useXmlGeneration() {
       return false
     }
 
-    if (!validateClaveAcceso(infoTributaria.claveAcceso)) {
-      toast.error('Clave de acceso inválida. Debe tener 49 dígitos')
-      return false
-    }
-
     if (!infoFactura.identificacionComprador) {
       toast.error('La identificación del comprador es requerida')
       return false
@@ -32,6 +28,16 @@ export function useXmlGeneration() {
       return false
     }
 
+    // GENERAR CLAVE DE ACCESO AUTOMÁTICAMENTE
+    const claveAcceso = generateClaveAcceso({
+      fechaEmision: infoFactura.fechaEmision,
+      ruc: infoTributaria.ruc,
+      ambiente: infoTributaria.ambiente,
+      establecimiento: infoTributaria.estab,
+      puntoEmision: infoTributaria.ptoEmi,
+      secuencial: infoTributaria.secuencial
+    })
+
     // Generar XML según formato real del SRI
     let xml = `<?xml version="1.0" encoding="UTF-8"?>
 <factura id="comprobante" version="1.1.0">
@@ -41,7 +47,7 @@ export function useXmlGeneration() {
     <razonSocial>${infoTributaria.razonSocial}</razonSocial>
     <nombreComercial>${infoTributaria.nombreComercial}</nombreComercial>
     <ruc>${infoTributaria.ruc}</ruc>
-    <claveAcceso>${infoTributaria.claveAcceso}</claveAcceso>
+    <claveAcceso>${claveAcceso}</claveAcceso>
     <codDoc>01</codDoc>
     <estab>${infoTributaria.estab}</estab>
     <ptoEmi>${infoTributaria.ptoEmi}</ptoEmi>
@@ -142,7 +148,7 @@ ${camposAdicionales.join('\n')}
 </factura>`
 
     onXmlGenerated(xml)
-    toast.success('XML generado exitosamente')
+    toast.success(`XML generado exitosamente.`,)
     return true
   }
 

+ 1 - 1
src/types/factura.ts

@@ -4,7 +4,7 @@ export interface InfoTributaria {
   razonSocial: string
   nombreComercial: string
   ruc: string
-  claveAcceso: string
+  claveAcceso?: string // Opcional - se genera automáticamente
   estab: string
   ptoEmi: string
   secuencial: string

+ 129 - 0
src/utils/factura/claveAcceso.ts

@@ -0,0 +1,129 @@
+/**
+ * Genera la clave de acceso de 49 dígitos según el algoritmo del SRI
+ * Basado en las especificaciones del Servicio de Rentas Internas de Ecuador
+ */
+
+interface GenerateAccessKeyParams {
+  fechaEmision: string // Formato: YYYY-MM-DD o DD/MM/YYYY
+  ruc: string // 13 dígitos
+  ambiente: string // "1" para pruebas, "2" para producción
+  establecimiento: string // 3 dígitos
+  puntoEmision: string // 3 dígitos
+  secuencial: string // 9 dígitos
+}
+
+/**
+ * Formatea una fecha al formato DDMMYYYY
+ */
+function formatDateToDDMMYYYY(dateString: string): string {
+  let date: Date
+
+  // Detectar formato de fecha
+  if (dateString.includes('-')) {
+    // Formato YYYY-MM-DD
+    date = new Date(dateString)
+  } else if (dateString.includes('/')) {
+    // Formato DD/MM/YYYY
+    const [day, month, year] = dateString.split('/')
+    date = new Date(`${year}-${month}-${day}`)
+  } else {
+    throw new Error('Formato de fecha no válido. Use YYYY-MM-DD o DD/MM/YYYY')
+  }
+
+  const day = date.getDate()
+  const month = date.getMonth() + 1 // getMonth() returns 0-11
+  const year = date.getFullYear()
+
+  // Pad day and month with a leading zero if they are less than 10
+  const finalDay = day < 10 ? '0' + day : day.toString()
+  const finalMonth = month < 10 ? '0' + month : month.toString()
+
+  return `${finalDay}${finalMonth}${year}`
+}
+
+/**
+ * Genera un número aleatorio de 8 dígitos
+ */
+function generateRandomEightDigitNumber(): string {
+  const min = 10000000
+  const max = 99999999
+  return (Math.floor(Math.random() * (max - min + 1)) + min).toString()
+}
+
+/**
+ * Calcula el dígito verificador usando el algoritmo de Módulo 11
+ */
+function generateVerificatorDigit(accessKey: string): number {
+  let addition = 0
+  let multiple = 7
+
+  for (let i = 0; i < accessKey.length; i++) {
+    addition += parseInt(accessKey.charAt(i)) * multiple
+    multiple > 2 ? multiple-- : (multiple = 7)
+  }
+
+  let result = 11 - (addition % 11)
+
+  // Reglas especiales del SRI
+  if (result === 10) result = 1
+  if (result === 11) result = 0
+
+  return result
+}
+
+/**
+ * Genera la clave de acceso completa de 49 dígitos
+ */
+export function generateClaveAcceso(params: GenerateAccessKeyParams): string {
+  let claveAcceso = ''
+
+  // 1. Fecha de emisión (8 dígitos: DDMMYYYY)
+  claveAcceso += formatDateToDDMMYYYY(params.fechaEmision)
+
+  // 2. Tipo de comprobante (2 dígitos - siempre "01" para facturas)
+  claveAcceso += '01'
+
+  // 3. Número de RUC (13 dígitos)
+  claveAcceso += params.ruc
+
+  // 4. Tipo de ambiente (1 dígito)
+  claveAcceso += params.ambiente
+
+  // 5. Establecimiento (3 dígitos)
+  claveAcceso += params.establecimiento
+
+  // 6. Punto de emisión (3 dígitos)
+  claveAcceso += params.puntoEmision
+
+  // 7. Secuencial (9 dígitos)
+  claveAcceso += params.secuencial.padStart(9, '0')
+
+  // 8. Código numérico aleatorio (8 dígitos)
+  claveAcceso += generateRandomEightDigitNumber()
+
+  // 9. Tipo de emisión (1 dígito - siempre "1" para emisión normal)
+  claveAcceso += '1'
+
+  // 10. Dígito verificador (1 dígito)
+  claveAcceso += generateVerificatorDigit(claveAcceso)
+
+  return claveAcceso
+}
+
+/**
+ * Valida que una clave de acceso tenga el formato correcto
+ */
+export function validateClaveAccesoFormat(claveAcceso: string): boolean {
+  // Debe tener exactamente 49 dígitos
+  if (claveAcceso.length !== 49) return false
+
+  // Debe contener solo dígitos
+  if (!/^\d+$/.test(claveAcceso)) return false
+
+  // Validar dígito verificador
+  const clave48 = claveAcceso.substring(0, 48)
+  const digitoVerificador = parseInt(claveAcceso.charAt(48))
+  const digitoCalculado = generateVerificatorDigit(clave48)
+
+  return digitoVerificador === digitoCalculado
+}