"use client" import { useState, useCallback, useMemo } from "react" import { Button } from "@/components/ui/button" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { Textarea } from "@/components/ui/textarea" import { toast } from "sonner" import { Trash2, Plus, Download } from "lucide-react" // Interfaces TypeScript para tipos estrictos interface InfoTributaria { ambiente: string tipoEmision: string razonSocial: string nombreComercial: string ruc: string claveAcceso: string estab: string ptoEmi: string secuencial: string dirMatriz: string } interface InfoFactura { fechaEmision: string dirEstablecimiento: string contribuyenteEspecial: string obligadoContabilidad: string tipoIdentificacionComprador: string razonSocialComprador: string identificacionComprador: string direccionComprador: string totalSinImpuestos: string totalDescuento: string codigoPorcentaje: string baseImponible: string valorImpuesto: string importeTotal: string formaPago: string emailComprador: string } interface DetalleItem { id: string codigoPrincipal: string descripcion: string cantidad: string precioUnitario: string descuento: string precioTotalSinImpuesto: string codigoPorcentaje: string tarifa: string baseImponible: string valorImpuesto: string } interface FacturaData { infoTributaria: InfoTributaria infoFactura: InfoFactura detalles: DetalleItem[] } // Componente para item de detalle function DetalleItemForm({ item, onChange, onRemove }: { item: DetalleItem onChange: (item: DetalleItem) => void onRemove: () => void }) { const handleChange = (field: keyof DetalleItem, value: string) => { const updatedItem = { ...item, [field]: value } // Cálculos automáticos if (field === 'cantidad' || field === 'precioUnitario' || field === 'descuento') { const cantidad = parseFloat(updatedItem.cantidad) || 0 const precioUnitario = parseFloat(updatedItem.precioUnitario) || 0 const descuento = parseFloat(updatedItem.descuento) || 0 const subtotal = cantidad * precioUnitario const precioTotalSinImpuesto = Math.max(0, subtotal - descuento) updatedItem.precioTotalSinImpuesto = precioTotalSinImpuesto.toFixed(2) updatedItem.baseImponible = precioTotalSinImpuesto.toFixed(2) // Calcular IVA (12% por defecto) const tarifa = parseFloat(updatedItem.tarifa) || 12 updatedItem.valorImpuesto = (precioTotalSinImpuesto * tarifa / 100).toFixed(2) } if (field === 'tarifa') { const baseImponible = parseFloat(updatedItem.baseImponible) || 0 const tarifa = parseFloat(value) || 12 updatedItem.valorImpuesto = (baseImponible * tarifa / 100).toFixed(2) } onChange(updatedItem) } return (

Detalle del Producto

handleChange('codigoPrincipal', e.target.value)} placeholder="PROD001" />
handleChange('descripcion', e.target.value)} placeholder="Descripción del producto" />
handleChange('cantidad', e.target.value)} placeholder="1" />
handleChange('precioUnitario', e.target.value)} placeholder="10.00" />
handleChange('descuento', e.target.value)} placeholder="0.00" />
) } // Funciones de validación específicas para Ecuador const validateRUC = (ruc: string): boolean => { // Validación básica de RUC ecuatoriano (13 dígitos) return /^\d{13}$/.test(ruc) } const validateCedula = (cedula: string): boolean => { // Validación básica de cédula ecuatoriana (10 dígitos) return /^\d{10}$/.test(cedula) } const validateClaveAcceso = (clave: string): boolean => { // Validación básica de clave de acceso SRI (49 dígitos) return /^\d{49}$/.test(clave) } export default function FacturaPage() { // Estado inicial const [infoTributaria, setInfoTributaria] = useState({ ambiente: '1', tipoEmision: '1', razonSocial: '', nombreComercial: '', ruc: '', claveAcceso: '', estab: '001', ptoEmi: '001', secuencial: '000000001', dirMatriz: '' }) const [infoFactura, setInfoFactura] = useState({ fechaEmision: new Date().toISOString().split('T')[0], dirEstablecimiento: '', contribuyenteEspecial: '', obligadoContabilidad: 'SI', tipoIdentificacionComprador: '04', razonSocialComprador: '', identificacionComprador: '', direccionComprador: '', totalSinImpuestos: '0.00', totalDescuento: '0.00', codigoPorcentaje: '2', baseImponible: '0.00', valorImpuesto: '0.00', importeTotal: '0.00', formaPago: '01', emailComprador: '' }) const [detalles, setDetalles] = useState([ { id: '1', codigoPrincipal: '', descripcion: '', cantidad: '1', precioUnitario: '0.00', descuento: '0.00', precioTotalSinImpuesto: '0.00', codigoPorcentaje: '2', tarifa: '12', baseImponible: '0.00', valorImpuesto: '0.00' } ]) const [xmlGenerado, setXmlGenerado] = useState('') // Calcular totales automáticamente const calcularTotales = useCallback(() => { let totalSinImpuestos = 0 let totalDescuento = 0 let baseImponibleIVA = 0 let valorIVA = 0 detalles.forEach(item => { const subtotal = parseFloat(item.precioTotalSinImpuesto) || 0 const descuento = parseFloat(item.descuento) || 0 const impuesto = parseFloat(item.valorImpuesto) || 0 totalSinImpuestos += subtotal totalDescuento += descuento baseImponibleIVA += subtotal valorIVA += impuesto }) const importeTotal = totalSinImpuestos + valorIVA setInfoFactura(prev => ({ ...prev, totalSinImpuestos: totalSinImpuestos.toFixed(2), totalDescuento: totalDescuento.toFixed(2), baseImponible: baseImponibleIVA.toFixed(2), valorImpuesto: valorIVA.toFixed(2), importeTotal: importeTotal.toFixed(2) })) }, [detalles]) // Recalcular totales cuando cambian los detalles useMemo(() => { calcularTotales() }, [detalles, calcularTotales]) // Manejar cambios en info tributaria const handleInfoTributariaChange = (field: keyof InfoTributaria, value: string) => { setInfoTributaria(prev => ({ ...prev, [field]: value })) } // Manejar cambios en info factura const handleInfoFacturaChange = (field: keyof InfoFactura, value: string) => { setInfoFactura(prev => ({ ...prev, [field]: value })) } // Agregar nuevo detalle const agregarDetalle = () => { const nuevoDetalle: DetalleItem = { id: Date.now().toString(), codigoPrincipal: '', descripcion: '', cantidad: '1', precioUnitario: '0.00', descuento: '0.00', precioTotalSinImpuesto: '0.00', codigoPorcentaje: '2', tarifa: '12', baseImponible: '0.00', valorImpuesto: '0.00' } setDetalles(prev => [...prev, nuevoDetalle]) } // Actualizar detalle const actualizarDetalle = (id: string, updatedItem: DetalleItem) => { setDetalles(prev => prev.map(item => item.id === id ? updatedItem : item)) } // Eliminar detalle const eliminarDetalle = (id: string) => { if (detalles.length > 1) { setDetalles(prev => prev.filter(item => item.id !== id)) toast.success('Item eliminado') } else { toast.error('Debe haber al menos un item') } } // Generar XML const generarXML = () => { // Validaciones básicas if (!validateRUC(infoTributaria.ruc)) { toast.error('RUC inválido. Debe tener 13 dígitos') return } if (!validateClaveAcceso(infoTributaria.claveAcceso)) { toast.error('Clave de acceso inválida. Debe tener 49 dígitos') return } if (!infoFactura.identificacionComprador) { toast.error('La identificación del comprador es requerida') return } if (infoFactura.tipoIdentificacionComprador === '04' && !validateRUC(infoFactura.identificacionComprador)) { toast.error('RUC del comprador inválido') return } // Generar XML (simulación del código PHP) let xml = ` ${infoTributaria.ambiente} ${infoTributaria.tipoEmision} ${infoTributaria.razonSocial} ${infoTributaria.nombreComercial} ${infoTributaria.ruc} ${infoTributaria.claveAcceso} 01 ${infoTributaria.estab} ${infoTributaria.ptoEmi} ${infoTributaria.secuencial} ${infoTributaria.dirMatriz} ${infoFactura.fechaEmision} ${infoFactura.dirEstablecimiento} ${infoFactura.contribuyenteEspecial} ${infoFactura.obligadoContabilidad} ${infoFactura.tipoIdentificacionComprador} ${infoFactura.razonSocialComprador} ${infoFactura.identificacionComprador} ${infoFactura.direccionComprador} ${infoFactura.totalSinImpuestos} ${infoFactura.totalDescuento} 2 ${infoFactura.codigoPorcentaje} ${infoFactura.baseImponible} ${infoFactura.valorImpuesto} 0.00 ${infoFactura.importeTotal} DOLAR ${infoFactura.formaPago} ${infoFactura.importeTotal} ` detalles.forEach(item => { xml += ` ${item.codigoPrincipal} ${item.descripcion} ${item.cantidad} ${item.precioUnitario} ${item.descuento} ${item.precioTotalSinImpuesto} 2 ${item.codigoPorcentaje} ${item.tarifa} ${item.baseImponible} ${item.valorImpuesto} ` }) xml += ` ${infoFactura.emailComprador} ` setXmlGenerado(xml) toast.success('XML generado exitosamente') } // Descargar XML const descargarXML = () => { if (!xmlGenerado) { toast.error('Debe generar el XML primero') return } const blob = new Blob([xmlGenerado], { type: 'application/xml' }) const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = `factura_${infoTributaria.ruc}_${new Date().getTime()}.xml` document.body.appendChild(a) a.click() document.body.removeChild(a) URL.revokeObjectURL(url) toast.success('XML descargado') } return (
{/* Header */}

Factura Electrónica SRI Ecuador

Generador de XML para facturación electrónica SRI

{/* Info Tributaria */} Información Tributaria Datos del emisor de la factura
handleInfoTributariaChange('ruc', e.target.value)} placeholder="13 dígitos" maxLength={13} />
handleInfoTributariaChange('razonSocial', e.target.value)} placeholder="Empresa S.A." />
handleInfoTributariaChange('nombreComercial', e.target.value)} placeholder="Nombre Comercial" />
handleInfoTributariaChange('claveAcceso', e.target.value)} placeholder="49 dígitos" maxLength={49} />
handleInfoTributariaChange('estab', e.target.value)} placeholder="001" maxLength={3} />
handleInfoTributariaChange('ptoEmi', e.target.value)} placeholder="001" maxLength={3} />
handleInfoTributariaChange('secuencial', e.target.value)} placeholder="000000001" maxLength={9} />
handleInfoTributariaChange('dirMatriz', e.target.value)} placeholder="Av. Principal 123 y Secundaria" />
{/* Info Factura */} Información de Factura Datos de la factura y comprador
handleInfoFacturaChange('fechaEmision', e.target.value)} />
handleInfoFacturaChange('dirEstablecimiento', e.target.value)} placeholder="Av. Secundaria 456" />
handleInfoFacturaChange('contribuyenteEspecial', e.target.value)} placeholder="Opcional" />
handleInfoFacturaChange('identificacionComprador', e.target.value)} placeholder="Según tipo seleccionado" />
handleInfoFacturaChange('razonSocialComprador', e.target.value)} placeholder="Nombre del comprador" />
handleInfoFacturaChange('direccionComprador', e.target.value)} placeholder="Dirección del comprador" />
handleInfoFacturaChange('emailComprador', e.target.value)} placeholder="email@ejemplo.com" />
{/* Detalles */}
Detalles de Productos/Servicios Agregar los productos o servicios de la factura
{detalles.map((item) => ( actualizarDetalle(item.id, updatedItem)} onRemove={() => eliminarDetalle(item.id)} /> ))}
{/* Totales */} Resumen de Totales
{/* Acciones */}
{xmlGenerado && ( )}
{/* XML Generado */} {xmlGenerado && ( XML Generado XML generado para el SRI