Forráskód Böngészése

fix invoice details

Matthew Trejo 1 hónapja
szülő
commit
b83dec2195
2 módosított fájl, 143 hozzáadás és 43 törlés
  1. 133 43
      src/app/factura/page.tsx
  2. 10 0
      src/components/app-sidebar.tsx

+ 133 - 43
src/app/factura/page.tsx

@@ -27,25 +27,27 @@ interface InfoTributaria {
 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
+  // Campos adicionales para infoAdicional
+  direccionComprador: string
   emailComprador: string
+  telefonoComprador: string
+  // Para pagos
+  plazo: string
+  unidadTiempo: string
 }
 
 interface DetalleItem {
   id: string
   codigoPrincipal: string
+  codigoAuxiliar: string
   descripcion: string
   cantidad: string
   precioUnitario: string
@@ -57,10 +59,18 @@ interface DetalleItem {
   valorImpuesto: string
 }
 
+interface TotalImpuesto {
+  codigo: string
+  codigoPorcentaje: string
+  baseImponible: string
+  valor: string
+}
+
 interface FacturaData {
   infoTributaria: InfoTributaria
   infoFactura: InfoFactura
   detalles: DetalleItem[]
+  totalesImpuestos: TotalImpuesto[]
 }
 
 // Componente para item de detalle
@@ -88,15 +98,29 @@ function DetalleItemForm({
       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)
+      // Calcular IVA solo si aplica
+      const tarifa = parseFloat(updatedItem.tarifa) || 0
+      const codigoPorcentaje = updatedItem.codigoPorcentaje
+      
+      // No calcular IVA si es "No objeto de IVA" (código 0) o tarifa 0%
+      if (codigoPorcentaje === '0' || tarifa === 0) {
+        updatedItem.valorImpuesto = '0.00'
+      } else {
+        updatedItem.valorImpuesto = (precioTotalSinImpuesto * tarifa / 100).toFixed(2)
+      }
     }
     
-    if (field === 'tarifa') {
+    if (field === 'tarifa' || field === 'codigoPorcentaje') {
       const baseImponible = parseFloat(updatedItem.baseImponible) || 0
-      const tarifa = parseFloat(value) || 12
-      updatedItem.valorImpuesto = (baseImponible * tarifa / 100).toFixed(2)
+      const tarifa = parseFloat(updatedItem.tarifa) || 0
+      const codigoPorcentaje = updatedItem.codigoPorcentaje
+      
+      // No calcular IVA si es "No objeto de IVA" (código 0) o tarifa 0%
+      if (codigoPorcentaje === '0' || tarifa === 0) {
+        updatedItem.valorImpuesto = '0.00'
+      } else {
+        updatedItem.valorImpuesto = (baseImponible * tarifa / 100).toFixed(2)
+      }
     }
     
     onChange(updatedItem)
@@ -127,6 +151,16 @@ function DetalleItemForm({
           />
         </div>
         
+        <div className="space-y-2">
+          <Label htmlFor={`codigo-auxiliar-${item.id}`}>Código Auxiliar</Label>
+          <Input
+            id={`codigo-auxiliar-${item.id}`}
+            value={item.codigoAuxiliar}
+            onChange={(e) => handleChange('codigoAuxiliar', e.target.value)}
+            placeholder="AUX001"
+          />
+        </div>
+        
         <div className="space-y-2">
           <Label htmlFor={`descripcion-${item.id}`}>Descripción</Label>
           <Input
@@ -198,9 +232,13 @@ function DetalleItemForm({
         
         <div className="space-y-2">
           <Label htmlFor={`tarifa-${item.id}`}>Tarifa IVA (%)</Label>
-          <Select value={item.tarifa} onValueChange={(value) => handleChange('tarifa', value)}>
+          <Select 
+            value={item.tarifa} 
+            onValueChange={(value) => handleChange('tarifa', value)}
+            disabled={item.codigoPorcentaje === '0'}
+          >
             <SelectTrigger>
-              <SelectValue placeholder="Seleccionar" />
+              <SelectValue placeholder={item.codigoPorcentaje === '0' ? 'No aplica' : 'Seleccionar'} />
             </SelectTrigger>
             <SelectContent>
               <SelectItem value="12">12%</SelectItem>
@@ -208,6 +246,11 @@ function DetalleItemForm({
               <SelectItem value="14">14%</SelectItem>
             </SelectContent>
           </Select>
+          {item.codigoPorcentaje === '0' && (
+            <p className="text-xs text-muted-foreground mt-1">
+              No aplica
+            </p>
+          )}
         </div>
         
         <div className="space-y-2">
@@ -258,26 +301,28 @@ export default function FacturaPage() {
   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: ''
+    // Campos adicionales para infoAdicional
+    direccionComprador: '',
+    emailComprador: '',
+    telefonoComprador: '',
+    // Para pagos
+    plazo: '0',
+    unidadTiempo: 'dias'
   })
 
   const [detalles, setDetalles] = useState<DetalleItem[]>([
     {
       id: '1',
       codigoPrincipal: '',
+      codigoAuxiliar: '',
       descripcion: '',
       cantidad: '1',
       precioUnitario: '0.00',
@@ -296,28 +341,41 @@ export default function FacturaPage() {
   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
     })
 
+    // Calcular totales de impuestos por tipo
+    const totalesImpuestos = new Map<string, { base: number, valor: number }>()
+    
+    detalles.forEach(item => {
+      const base = parseFloat(item.baseImponible) || 0
+      const valor = parseFloat(item.valorImpuesto) || 0
+      const key = item.codigoPorcentaje
+      
+      if (totalesImpuestos.has(key)) {
+        const current = totalesImpuestos.get(key)!
+        totalesImpuestos.set(key, { 
+          base: current.base + base, 
+          valor: current.valor + valor 
+        })
+      } else {
+        totalesImpuestos.set(key, { base, valor })
+      }
+    })
+
+    const valorIVA = Array.from(totalesImpuestos.values()).reduce((sum, item) => sum + item.valor, 0)
     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])
@@ -342,6 +400,7 @@ export default function FacturaPage() {
     const nuevoDetalle: DetalleItem = {
       id: Date.now().toString(),
       codigoPrincipal: '',
+      codigoAuxiliar: '',
       descripcion: '',
       cantidad: '1',
       precioUnitario: '0.00',
@@ -393,7 +452,26 @@ export default function FacturaPage() {
       return
     }
 
-    // Generar XML (simulación del código PHP)
+    // Calcular totales de impuestos por tipo
+    const totalesImpuestos = new Map<string, { base: number, valor: number }>()
+    
+    detalles.forEach(item => {
+      const base = parseFloat(item.baseImponible) || 0
+      const valor = parseFloat(item.valorImpuesto) || 0
+      const key = item.codigoPorcentaje
+      
+      if (totalesImpuestos.has(key)) {
+        const current = totalesImpuestos.get(key)!
+        totalesImpuestos.set(key, { 
+          base: current.base + base, 
+          valor: current.valor + valor 
+        })
+      } else {
+        totalesImpuestos.set(key, { base, valor })
+      }
+    })
+
+    // Generar XML según formato real del SRI
     let xml = `<?xml version="1.0" encoding="UTF-8"?>
 <factura id="comprobante" version="1.1.0">
   <infoTributaria>
@@ -413,24 +491,28 @@ export default function FacturaPage() {
   <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>
+    <totalConImpuestos>`
+
+    // Agregar múltiples impuestos según los detalles
+    totalesImpuestos.forEach((impuesto, codigoPorcentaje) => {
+      xml += `
       <totalImpuesto>
         <codigo>2</codigo>
-        <codigoPorcentaje>${infoFactura.codigoPorcentaje}</codigoPorcentaje>
-        <baseImponible>${infoFactura.baseImponible}</baseImponible>
-        <valor>${infoFactura.valorImpuesto}</valor>
-      </totalImpuesto>
+        <codigoPorcentaje>${codigoPorcentaje}</codigoPorcentaje>
+        <baseImponible>${impuesto.base.toFixed(2)}</baseImponible>
+        <valor>${impuesto.valor.toFixed(2)}</valor>
+      </totalImpuesto>`
+    })
+
+    xml += `
     </totalConImpuestos>
-    
     <propina>0.00</propina>
     <importeTotal>${infoFactura.importeTotal}</importeTotal>
     <moneda>DOLAR</moneda>
@@ -439,6 +521,8 @@ export default function FacturaPage() {
       <pago>
         <formaPago>${infoFactura.formaPago}</formaPago>
         <total>${infoFactura.importeTotal}</total>
+        <plazo>${infoFactura.plazo}</plazo>
+        <unidadTiempo>${infoFactura.unidadTiempo}</unidadTiempo>
       </pago>
     </pagos>
   </infoFactura>
@@ -449,9 +533,10 @@ export default function FacturaPage() {
       xml += `
     <detalle>
       <codigoPrincipal>${item.codigoPrincipal}</codigoPrincipal>
+      <codigoAuxiliar>${item.codigoAuxiliar}</codigoAuxiliar>
       <descripcion>${item.descripcion}</descripcion>
       <cantidad>${item.cantidad}</cantidad>
-      <precioUnitario>${item.precioUnitario}</precioUnitario>
+      <precioUnitario>${parseFloat(item.precioUnitario).toFixed(6)}</precioUnitario>
       <descuento>${item.descuento}</descuento>
       <precioTotalSinImpuesto>${item.precioTotalSinImpuesto}</precioTotalSinImpuesto>
       
@@ -471,7 +556,9 @@ export default function FacturaPage() {
   </detalles>
   
   <infoAdicional>
+    <campoAdicional nombre="Direccion">${infoFactura.direccionComprador}</campoAdicional>
     <campoAdicional nombre="Email">${infoFactura.emailComprador}</campoAdicional>
+    <campoAdicional nombre="Telefono">${infoFactura.telefonoComprador}</campoAdicional>
   </infoAdicional>
 </factura>`
 
@@ -658,12 +745,12 @@ export default function FacturaPage() {
               </div>
               
               <div className="space-y-2">
-                <Label htmlFor="contribuyenteEspecial">Contribuyente Especial</Label>
+                <Label htmlFor="telefonoComprador">Teléfono Comprador</Label>
                 <Input
-                  id="contribuyenteEspecial"
-                  value={infoFactura.contribuyenteEspecial}
-                  onChange={(e) => handleInfoFacturaChange('contribuyenteEspecial', e.target.value)}
-                  placeholder="Opcional"
+                  id="telefonoComprador"
+                  value={infoFactura.telefonoComprador}
+                  onChange={(e) => handleInfoFacturaChange('telefonoComprador', e.target.value)}
+                  placeholder="Teléfono del comprador"
                 />
               </div>
               
@@ -793,8 +880,11 @@ export default function FacturaPage() {
                 <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" />
+                <Label>Valor IVA Total</Label>
+                <Input value={(() => {
+                  const totalIVA = detalles.reduce((sum, item) => sum + (parseFloat(item.valorImpuesto) || 0), 0)
+                  return totalIVA.toFixed(2)
+                })()} readOnly className="bg-muted" />
               </div>
               <div className="space-y-2">
                 <Label>Importe Total</Label>

+ 10 - 0
src/components/app-sidebar.tsx

@@ -22,6 +22,7 @@ import {
   SidebarProvider,
   SidebarTrigger,
 } from "@/components/ui/sidebar"
+import { ModeToggle } from "@/components/mode-toggle"
 
 // Menu items
 const items = [
@@ -81,6 +82,15 @@ export function AppSidebar() {
       </SidebarContent>
       <SidebarFooter>
         <SidebarMenu>
+          <SidebarMenuItem>
+            <div className="flex items-center justify-between w-full px-2 py-1">
+              <div className="grid flex-1 text-left text-sm leading-tight">
+                <span className="truncate font-semibold">Tema</span>
+                <span className="truncate text-xs">Cambiar modo</span>
+              </div>
+              <ModeToggle />
+            </div>
+          </SidebarMenuItem>
           <SidebarMenuItem>
             <DropdownMenu>
               <DropdownMenuTrigger asChild>