| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839 |
- "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 (
- <Card className="p-4">
- <div className="flex justify-between items-center mb-4">
- <h4 className="text-sm font-medium">Detalle del Producto</h4>
- <Button
- type="button"
- variant="destructive"
- size="sm"
- onClick={onRemove}
- >
- <Trash2 className="h-4 w-4" />
- </Button>
- </div>
-
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
- <div className="space-y-2">
- <Label htmlFor={`codigo-${item.id}`}>Código Principal</Label>
- <Input
- id={`codigo-${item.id}`}
- value={item.codigoPrincipal}
- onChange={(e) => handleChange('codigoPrincipal', e.target.value)}
- placeholder="PROD001"
- />
- </div>
-
- <div className="space-y-2">
- <Label htmlFor={`descripcion-${item.id}`}>Descripción</Label>
- <Input
- id={`descripcion-${item.id}`}
- value={item.descripcion}
- onChange={(e) => handleChange('descripcion', e.target.value)}
- placeholder="Descripción del producto"
- />
- </div>
-
- <div className="space-y-2">
- <Label htmlFor={`cantidad-${item.id}`}>Cantidad</Label>
- <Input
- id={`cantidad-${item.id}`}
- type="number"
- step="0.01"
- value={item.cantidad}
- onChange={(e) => handleChange('cantidad', e.target.value)}
- placeholder="1"
- />
- </div>
-
- <div className="space-y-2">
- <Label htmlFor={`precio-unitario-${item.id}`}>Precio Unitario</Label>
- <Input
- id={`precio-unitario-${item.id}`}
- type="number"
- step="0.01"
- value={item.precioUnitario}
- onChange={(e) => handleChange('precioUnitario', e.target.value)}
- placeholder="10.00"
- />
- </div>
-
- <div className="space-y-2">
- <Label htmlFor={`descuento-${item.id}`}>Descuento</Label>
- <Input
- id={`descuento-${item.id}`}
- type="number"
- step="0.01"
- value={item.descuento}
- onChange={(e) => handleChange('descuento', e.target.value)}
- placeholder="0.00"
- />
- </div>
-
- <div className="space-y-2">
- <Label htmlFor={`precio-total-${item.id}`}>Total sin Impuesto</Label>
- <Input
- id={`precio-total-${item.id}`}
- value={item.precioTotalSinImpuesto}
- readOnly
- className="bg-muted"
- />
- </div>
-
- <div className="space-y-2">
- <Label htmlFor={`codigo-porcentaje-${item.id}`}>Código IVA</Label>
- <Select value={item.codigoPorcentaje} onValueChange={(value) => handleChange('codigoPorcentaje', value)}>
- <SelectTrigger>
- <SelectValue placeholder="Seleccionar" />
- </SelectTrigger>
- <SelectContent>
- <SelectItem value="2">2 - IVA</SelectItem>
- <SelectItem value="0">0 - No objeto de IVA</SelectItem>
- </SelectContent>
- </Select>
- </div>
-
- <div className="space-y-2">
- <Label htmlFor={`tarifa-${item.id}`}>Tarifa IVA (%)</Label>
- <Select value={item.tarifa} onValueChange={(value) => handleChange('tarifa', value)}>
- <SelectTrigger>
- <SelectValue placeholder="Seleccionar" />
- </SelectTrigger>
- <SelectContent>
- <SelectItem value="12">12%</SelectItem>
- <SelectItem value="0">0%</SelectItem>
- <SelectItem value="14">14%</SelectItem>
- </SelectContent>
- </Select>
- </div>
-
- <div className="space-y-2">
- <Label htmlFor={`valor-impuesto-${item.id}`}>Valor Impuesto</Label>
- <Input
- id={`valor-impuesto-${item.id}`}
- value={item.valorImpuesto}
- readOnly
- className="bg-muted"
- />
- </div>
- </div>
- </Card>
- )
- }
- // 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<InfoTributaria>({
- ambiente: '1',
- tipoEmision: '1',
- razonSocial: '',
- nombreComercial: '',
- ruc: '',
- claveAcceso: '',
- estab: '001',
- ptoEmi: '001',
- secuencial: '000000001',
- dirMatriz: ''
- })
- const [infoFactura, setInfoFactura] = useState<InfoFactura>({
- 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<DetalleItem[]>([
- {
- 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 = `<?xml version="1.0" encoding="UTF-8"?>
- <factura id="comprobante" version="1.1.0">
- <infoTributaria>
- <ambiente>${infoTributaria.ambiente}</ambiente>
- <tipoEmision>${infoTributaria.tipoEmision}</tipoEmision>
- <razonSocial>${infoTributaria.razonSocial}</razonSocial>
- <nombreComercial>${infoTributaria.nombreComercial}</nombreComercial>
- <ruc>${infoTributaria.ruc}</ruc>
- <claveAcceso>${infoTributaria.claveAcceso}</claveAcceso>
- <codDoc>01</codDoc>
- <estab>${infoTributaria.estab}</estab>
- <ptoEmi>${infoTributaria.ptoEmi}</ptoEmi>
- <secuencial>${infoTributaria.secuencial}</secuencial>
- <dirMatriz>${infoTributaria.dirMatriz}</dirMatriz>
- </infoTributaria>
-
- <infoFactura>
- <fechaEmision>${infoFactura.fechaEmision}</fechaEmision>
- <dirEstablecimiento>${infoFactura.dirEstablecimiento}</dirEstablecimiento>
- <contribuyenteEspecial>${infoFactura.contribuyenteEspecial}</contribuyenteEspecial>
- <obligadoContabilidad>${infoFactura.obligadoContabilidad}</obligadoContabilidad>
- <tipoIdentificacionComprador>${infoFactura.tipoIdentificacionComprador}</tipoIdentificacionComprador>
- <razonSocialComprador>${infoFactura.razonSocialComprador}</razonSocialComprador>
- <identificacionComprador>${infoFactura.identificacionComprador}</identificacionComprador>
- <direccionComprador>${infoFactura.direccionComprador}</direccionComprador>
- <totalSinImpuestos>${infoFactura.totalSinImpuestos}</totalSinImpuestos>
- <totalDescuento>${infoFactura.totalDescuento}</totalDescuento>
-
- <totalConImpuestos>
- <totalImpuesto>
- <codigo>2</codigo>
- <codigoPorcentaje>${infoFactura.codigoPorcentaje}</codigoPorcentaje>
- <baseImponible>${infoFactura.baseImponible}</baseImponible>
- <valor>${infoFactura.valorImpuesto}</valor>
- </totalImpuesto>
- </totalConImpuestos>
-
- <propina>0.00</propina>
- <importeTotal>${infoFactura.importeTotal}</importeTotal>
- <moneda>DOLAR</moneda>
-
- <pagos>
- <pago>
- <formaPago>${infoFactura.formaPago}</formaPago>
- <total>${infoFactura.importeTotal}</total>
- </pago>
- </pagos>
- </infoFactura>
-
- <detalles>`
- detalles.forEach(item => {
- xml += `
- <detalle>
- <codigoPrincipal>${item.codigoPrincipal}</codigoPrincipal>
- <descripcion>${item.descripcion}</descripcion>
- <cantidad>${item.cantidad}</cantidad>
- <precioUnitario>${item.precioUnitario}</precioUnitario>
- <descuento>${item.descuento}</descuento>
- <precioTotalSinImpuesto>${item.precioTotalSinImpuesto}</precioTotalSinImpuesto>
-
- <impuestos>
- <impuesto>
- <codigo>2</codigo>
- <codigoPorcentaje>${item.codigoPorcentaje}</codigoPorcentaje>
- <tarifa>${item.tarifa}</tarifa>
- <baseImponible>${item.baseImponible}</baseImponible>
- <valor>${item.valorImpuesto}</valor>
- </impuesto>
- </impuestos>
- </detalle>`
- })
- xml += `
- </detalles>
-
- <infoAdicional>
- <campoAdicional nombre="Email">${infoFactura.emailComprador}</campoAdicional>
- </infoAdicional>
- </factura>`
- 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 (
- <div className="space-y-6">
- {/* Header */}
- <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>
- {/* Info Tributaria */}
- <Card>
- <CardHeader>
- <CardTitle>Información Tributaria</CardTitle>
- <CardDescription>Datos del emisor de la factura</CardDescription>
- </CardHeader>
- <CardContent>
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
- <div className="space-y-2">
- <Label htmlFor="ambiente">Ambiente</Label>
- <Select value={infoTributaria.ambiente} onValueChange={(value) => handleInfoTributariaChange('ambiente', value)}>
- <SelectTrigger>
- <SelectValue />
- </SelectTrigger>
- <SelectContent>
- <SelectItem value="1">1 - Pruebas</SelectItem>
- <SelectItem value="2">2 - Producción</SelectItem>
- </SelectContent>
- </Select>
- </div>
-
- <div className="space-y-2">
- <Label htmlFor="tipoEmision">Tipo Emisión</Label>
- <Select value={infoTributaria.tipoEmision} onValueChange={(value) => handleInfoTributariaChange('tipoEmision', value)}>
- <SelectTrigger>
- <SelectValue />
- </SelectTrigger>
- <SelectContent>
- <SelectItem value="1">1 - Normal</SelectItem>
- </SelectContent>
- </Select>
- </div>
-
- <div className="space-y-2">
- <Label htmlFor="ruc">RUC *</Label>
- <Input
- id="ruc"
- value={infoTributaria.ruc}
- onChange={(e) => handleInfoTributariaChange('ruc', e.target.value)}
- placeholder="13 dígitos"
- maxLength={13}
- />
- </div>
-
- <div className="space-y-2">
- <Label htmlFor="razonSocial">Razón Social *</Label>
- <Input
- id="razonSocial"
- value={infoTributaria.razonSocial}
- onChange={(e) => handleInfoTributariaChange('razonSocial', e.target.value)}
- placeholder="Empresa S.A."
- />
- </div>
-
- <div className="space-y-2">
- <Label htmlFor="nombreComercial">Nombre Comercial *</Label>
- <Input
- id="nombreComercial"
- value={infoTributaria.nombreComercial}
- onChange={(e) => handleInfoTributariaChange('nombreComercial', e.target.value)}
- placeholder="Nombre Comercial"
- />
- </div>
-
- <div className="space-y-2">
- <Label htmlFor="claveAcceso">Clave de Acceso *</Label>
- <Input
- id="claveAcceso"
- value={infoTributaria.claveAcceso}
- onChange={(e) => handleInfoTributariaChange('claveAcceso', e.target.value)}
- placeholder="49 dígitos"
- maxLength={49}
- />
- </div>
-
- <div className="space-y-2">
- <Label htmlFor="estab">Establecimiento</Label>
- <Input
- id="estab"
- value={infoTributaria.estab}
- onChange={(e) => handleInfoTributariaChange('estab', e.target.value)}
- placeholder="001"
- maxLength={3}
- />
- </div>
-
- <div className="space-y-2">
- <Label htmlFor="ptoEmi">Punto Emisión</Label>
- <Input
- id="ptoEmi"
- value={infoTributaria.ptoEmi}
- onChange={(e) => handleInfoTributariaChange('ptoEmi', e.target.value)}
- placeholder="001"
- maxLength={3}
- />
- </div>
-
- <div className="space-y-2">
- <Label htmlFor="secuencial">Secuencial</Label>
- <Input
- id="secuencial"
- value={infoTributaria.secuencial}
- onChange={(e) => handleInfoTributariaChange('secuencial', e.target.value)}
- placeholder="000000001"
- maxLength={9}
- />
- </div>
-
- <div className="space-y-2 lg:col-span-3">
- <Label htmlFor="dirMatriz">Dirección Matriz *</Label>
- <Input
- id="dirMatriz"
- value={infoTributaria.dirMatriz}
- onChange={(e) => handleInfoTributariaChange('dirMatriz', e.target.value)}
- placeholder="Av. Principal 123 y Secundaria"
- />
- </div>
- </div>
- </CardContent>
- </Card>
- {/* Info Factura */}
- <Card>
- <CardHeader>
- <CardTitle>Información de Factura</CardTitle>
- <CardDescription>Datos de la factura y comprador</CardDescription>
- </CardHeader>
- <CardContent>
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
- <div className="space-y-2">
- <Label htmlFor="fechaEmision">Fecha Emisión *</Label>
- <Input
- id="fechaEmision"
- type="date"
- value={infoFactura.fechaEmision}
- onChange={(e) => handleInfoFacturaChange('fechaEmision', e.target.value)}
- />
- </div>
-
- <div className="space-y-2">
- <Label htmlFor="dirEstablecimiento">Dirección Establecimiento *</Label>
- <Input
- id="dirEstablecimiento"
- value={infoFactura.dirEstablecimiento}
- onChange={(e) => handleInfoFacturaChange('dirEstablecimiento', e.target.value)}
- placeholder="Av. Secundaria 456"
- />
- </div>
-
- <div className="space-y-2">
- <Label htmlFor="contribuyenteEspecial">Contribuyente Especial</Label>
- <Input
- id="contribuyenteEspecial"
- value={infoFactura.contribuyenteEspecial}
- onChange={(e) => handleInfoFacturaChange('contribuyenteEspecial', e.target.value)}
- placeholder="Opcional"
- />
- </div>
-
- <div className="space-y-2">
- <Label htmlFor="obligadoContabilidad">Obligado Contabilidad</Label>
- <Select value={infoFactura.obligadoContabilidad} onValueChange={(value) => handleInfoFacturaChange('obligadoContabilidad', value)}>
- <SelectTrigger>
- <SelectValue />
- </SelectTrigger>
- <SelectContent>
- <SelectItem value="SI">SI</SelectItem>
- <SelectItem value="NO">NO</SelectItem>
- </SelectContent>
- </Select>
- </div>
-
- <div className="space-y-2">
- <Label htmlFor="tipoIdentificacionComprador">Tipo Identificación Comprador</Label>
- <Select value={infoFactura.tipoIdentificacionComprador} onValueChange={(value) => handleInfoFacturaChange('tipoIdentificacionComprador', value)}>
- <SelectTrigger>
- <SelectValue />
- </SelectTrigger>
- <SelectContent>
- <SelectItem value="04">04 - RUC</SelectItem>
- <SelectItem value="05">05 - Cédula</SelectItem>
- <SelectItem value="06">06 - Pasaporte</SelectItem>
- <SelectItem value="07">07 - Consumidor Final</SelectItem>
- </SelectContent>
- </Select>
- </div>
-
- <div className="space-y-2">
- <Label htmlFor="identificacionComprador">Identificación Comprador *</Label>
- <Input
- id="identificacionComprador"
- value={infoFactura.identificacionComprador}
- onChange={(e) => handleInfoFacturaChange('identificacionComprador', e.target.value)}
- placeholder="Según tipo seleccionado"
- />
- </div>
-
- <div className="space-y-2">
- <Label htmlFor="razonSocialComprador">Razón Social Comprador *</Label>
- <Input
- id="razonSocialComprador"
- value={infoFactura.razonSocialComprador}
- onChange={(e) => handleInfoFacturaChange('razonSocialComprador', e.target.value)}
- placeholder="Nombre del comprador"
- />
- </div>
-
- <div className="space-y-2">
- <Label htmlFor="direccionComprador">Dirección Comprador *</Label>
- <Input
- id="direccionComprador"
- value={infoFactura.direccionComprador}
- onChange={(e) => handleInfoFacturaChange('direccionComprador', e.target.value)}
- placeholder="Dirección del comprador"
- />
- </div>
-
- <div className="space-y-2">
- <Label htmlFor="formaPago">Forma de Pago</Label>
- <Select value={infoFactura.formaPago} onValueChange={(value) => handleInfoFacturaChange('formaPago', value)}>
- <SelectTrigger>
- <SelectValue />
- </SelectTrigger>
- <SelectContent>
- <SelectItem value="01">01 - Efectivo</SelectItem>
- <SelectItem value="15">15 - Transferencia</SelectItem>
- <SelectItem value="16">16 - Tarjeta Crédito</SelectItem>
- <SelectItem value="17">17 - Tarjeta Débito</SelectItem>
- <SelectItem value="20">20 - Otros</SelectItem>
- </SelectContent>
- </Select>
- </div>
-
- <div className="space-y-2">
- <Label htmlFor="emailComprador">Email Comprador</Label>
- <Input
- id="emailComprador"
- type="email"
- value={infoFactura.emailComprador}
- onChange={(e) => handleInfoFacturaChange('emailComprador', e.target.value)}
- placeholder="email@ejemplo.com"
- />
- </div>
- </div>
- </CardContent>
- </Card>
- {/* Detalles */}
- <Card>
- <CardHeader>
- <div className="flex justify-between items-center">
- <div>
- <CardTitle>Detalles de Productos/Servicios</CardTitle>
- <CardDescription>Agregar los productos o servicios de la factura</CardDescription>
- </div>
- <Button type="button" onClick={agregarDetalle} className="flex items-center gap-2">
- <Plus className="h-4 w-4" />
- Agregar Item
- </Button>
- </div>
- </CardHeader>
- <CardContent className="space-y-4">
- {detalles.map((item) => (
- <DetalleItemForm
- key={item.id}
- item={item}
- onChange={(updatedItem) => actualizarDetalle(item.id, updatedItem)}
- onRemove={() => eliminarDetalle(item.id)}
- />
- ))}
- </CardContent>
- </Card>
- {/* Totales */}
- <Card>
- <CardHeader>
- <CardTitle>Resumen de Totales</CardTitle>
- </CardHeader>
- <CardContent>
- <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
- <div className="space-y-2">
- <Label>Total Sin Impuestos</Label>
- <Input value={infoFactura.totalSinImpuestos} readOnly className="bg-muted" />
- </div>
- <div className="space-y-2">
- <Label>Valor IVA</Label>
- <Input value={infoFactura.valorImpuesto} readOnly className="bg-muted" />
- </div>
- <div className="space-y-2">
- <Label>Importe Total</Label>
- <Input value={infoFactura.importeTotal} readOnly className="bg-muted font-bold text-lg" />
- </div>
- </div>
- </CardContent>
- </Card>
- {/* Acciones */}
- <div className="flex gap-4">
- <Button onClick={generarXML} className="flex-1">
- Generar XML
- </Button>
- {xmlGenerado && (
- <Button variant="outline" onClick={descargarXML} className="flex items-center gap-2">
- <Download className="h-4 w-4" />
- Descargar XML
- </Button>
- )}
- </div>
- {/* XML Generado */}
- {xmlGenerado && (
- <Card>
- <CardHeader>
- <CardTitle>XML Generado</CardTitle>
- <CardDescription>XML generado para el SRI</CardDescription>
- </CardHeader>
- <CardContent>
- <Textarea
- value={xmlGenerado}
- readOnly
- rows={20}
- className="font-mono text-sm"
- />
- </CardContent>
- </Card>
- )}
- </div>
- )
- }
|