Quellcode durchsuchen

use modal for xml sign result

Matthew Trejo vor 1 Monat
Ursprung
Commit
109feef106

BIN
prisma/dev.db


+ 1 - 0
src/app/api/poll-sri/route.ts

@@ -16,6 +16,7 @@ export async function POST(request: NextRequest) {
 
     // Validar ambiente
     const ambienteValido = ambiente === 'produccion' ? 'produccion' : 'pruebas'
+    console.log(`Usando ambiente para verificación: ${ambienteValido}`)
 
     // Verificar el estado del documento en el SRI
     // longPollDoc hace polling hasta que el documento sea procesado

+ 1 - 0
src/app/api/send-to-sri/route.ts

@@ -16,6 +16,7 @@ export async function POST(request: NextRequest) {
 
     // Validar ambiente
     const ambienteValido = ambiente === 'produccion' ? 'produccion' : 'pruebas'
+    console.log(`Usando ambiente: ${ambienteValido}`)
 
     // Enviar el documento firmado al SRI
     const result = await sendDocToSRI(signedXml, ambienteValido)

+ 6 - 2
src/app/enviar-sri/page.tsx

@@ -13,7 +13,8 @@ export default function EnviarSRIPage() {
     password,
     signedXml,
     accessKey,
-    ambiente,
+    ambienteDetectado,
+    ambienteEnvio,
     isLoading,
     estadoEnvio,
     respuestaAutorizacion,
@@ -21,6 +22,7 @@ export default function EnviarSRIPage() {
     setP12File,
     setPassword,
     setAccessKey,
+    setAmbienteEnvio,
     handleSign,
     handleEnviarSRI,
     handleVerificarEstado,
@@ -39,13 +41,15 @@ export default function EnviarSRIPage() {
         p12File={p12File}
         password={password}
         accessKey={accessKey}
-        ambiente={ambiente}
+        ambiente={ambienteEnvio}
+        ambienteDetectado={ambienteDetectado}
         signedXml={signedXml}
         isLoading={isLoading}
         onXmlFileChange={setXmlFile}
         onP12FileChange={setP12File}
         onPasswordChange={setPassword}
         onAccessKeyChange={setAccessKey}
+        onAmbienteChange={setAmbienteEnvio}
         onLoadSignedXml={handleLoadSignedXml}
       />
 

+ 19 - 6
src/app/firmar/page.tsx

@@ -1,12 +1,15 @@
 "use client"
 
+import { useState } from "react"
 import { FirmarHeader } from "@/components/firmar/FirmarHeader"
 import { FileUploadSection } from "@/components/firmar/FileUploadSection"
-import { SignedXmlPreview } from "@/components/firmar/SignedXmlPreview"
 import { SignActions } from "@/components/firmar/SignActions"
+import { XmlGenerationDialog } from "@/components/factura/XmlGenerationDialog"
 import { useXmlSigning } from "@/hooks/firmar/useXmlSigning"
 
 export default function FirmarPage() {
+  const [dialogOpen, setDialogOpen] = useState(false)
+
   const {
     xmlFile,
     p12File,
@@ -21,6 +24,13 @@ export default function FirmarPage() {
     handleReset,
   } = useXmlSigning()
 
+  const handleSignClick = async () => {
+    const success = await handleSign()
+    if (success) {
+      setDialogOpen(true)
+    }
+  }
+
   return (
     <div className="container mx-auto max-w-4xl py-8 space-y-8">
       <FirmarHeader />
@@ -40,13 +50,16 @@ export default function FirmarPage() {
         p12File={p12File}
         password={password}
         isLoading={isLoading}
-        signedXml={signedXml}
-        onSign={handleSign}
-        onDownload={handleDownload}
-        onReset={handleReset}
+        onSign={handleSignClick}
       />
 
-      {signedXml && <SignedXmlPreview signedXml={signedXml} />}
+      {/* Modal de XML Firmado */}
+      <XmlGenerationDialog
+        open={dialogOpen}
+        onOpenChange={setDialogOpen}
+        xmlGenerado={signedXml || ""}
+        onDescargarXml={handleDownload}
+      />
     </div>
   )
 }

+ 107 - 77
src/components/envio-sri/ParametrosSRIForm.tsx

@@ -7,13 +7,15 @@ import { toast } from "sonner"
 import { Separator } from "@/components/ui/separator"
 import { Button } from "@/components/ui/button"
 import { Badge } from "@/components/ui/badge"
+import { ServidorSelector } from "./ServidorSelector"
 
 interface ParametrosSRIFormProps {
   xmlFile: File | null
   p12File: File | null
   password: string
   accessKey: string
-  ambiente: '1' | '2'
+  ambiente: '1' | '2' // Ambiente de envío seleccionado por usuario
+  ambienteDetectado: '1' | '2' | null // Ambiente detectado del XML (solo lectura)
   signedXml: string | null
   isLoading: boolean
   onXmlFileChange: (file: File | null) => void
@@ -21,6 +23,7 @@ interface ParametrosSRIFormProps {
   onPasswordChange: (password: string) => void
   onAccessKeyChange: (key: string) => void
   onLoadSignedXml: (file: File) => void
+  onAmbienteChange: (ambiente: '1' | '2') => void
 }
 
 export function ParametrosSRIForm({
@@ -29,6 +32,7 @@ export function ParametrosSRIForm({
   password,
   accessKey,
   ambiente,
+  ambienteDetectado,
   signedXml,
   isLoading,
   onXmlFileChange,
@@ -36,89 +40,115 @@ export function ParametrosSRIForm({
   onPasswordChange,
   onAccessKeyChange,
   onLoadSignedXml,
+  onAmbienteChange,
 }: ParametrosSRIFormProps) {
   return (
-    <Card>
-      <CardHeader>
-        <CardTitle>Documentos y Parámetros</CardTitle>
-        <CardDescription>
-          Carga tu XML firmado y parámetros necesarios se llenan automáticamente
-        </CardDescription>
-      </CardHeader>
-      <CardContent className="space-y-6">
-        {/* Archivo XML Firmado */}
-        <div className="space-y-2">
-          <Dropzone
-            accept={{
-              "text/xml": [".xml"],
-              "application/xml": [".xml"],
-            }}
-            maxFiles={1}
-            onDrop={(acceptedFiles) => {
-              if (acceptedFiles.length > 0) {
-                onLoadSignedXml(acceptedFiles[0])
-              }
-            }}
-            disabled={isLoading}
-            className="border-2 border-dashed rounded-lg p-6"
-          >
-            <div className="flex flex-col items-center gap-2 text-center">
-              <div className="rounded-full bg-primary/10 p-3">
-                <Upload className="h-5 w-5 text-primary" />
+    <div className="space-y-6">
+      <Card>
+        <CardHeader>
+          <CardTitle>Documentos y Parámetros</CardTitle>
+          <CardDescription>
+            Carga tu XML firmado y parámetros necesarios se llenan automáticamente
+          </CardDescription>
+        </CardHeader>
+        <CardContent className="space-y-6">
+          {/* Archivo XML Firmado */}
+          <div className="space-y-2">
+            <Dropzone
+              accept={{
+                "text/xml": [".xml"],
+                "application/xml": [".xml"],
+              }}
+              maxFiles={1}
+              onDrop={(acceptedFiles) => {
+                if (acceptedFiles.length > 0) {
+                  onLoadSignedXml(acceptedFiles[0])
+                }
+              }}
+              disabled={isLoading}
+              className="border-2 border-dashed rounded-lg p-6"
+            >
+              <div className="flex flex-col items-center gap-2 text-center">
+                <div className="rounded-full bg-primary/10 p-3">
+                  <Upload className="h-5 w-5 text-primary" />
+                </div>
+                <div className="space-y-1">
+                  <p className="text-sm font-medium">
+                    {signedXml && xmlFile ? (
+                      <span className="flex items-center gap-2">
+                        <Check className="h-4 w-4 text-green-500" />
+                        {xmlFile.name}
+                      </span>
+                    ) : (
+                      "Arrastra un XML firmado aquí"
+                    )}
+                  </p>
+                </div>
               </div>
-              <div className="space-y-1">
-                <p className="text-sm font-medium">
-                  {signedXml && xmlFile ? (
-                    <span className="flex items-center gap-2">
-                      <Check className="h-4 w-4 text-green-500" />
-                      {xmlFile.name}
-                    </span>
-                  ) : (
-                    "Arrastra un XML firmado aquí"
-                  )}
-                </p>
-              </div>
-            </div>
-          </Dropzone>
-        </div>
-        <Separator />
-
-        {/* Información del XML (solo lectura) */}
-        {signedXml && accessKey && (
-          <div className="space-y-4 p-4 bg-muted/50 rounded-lg">
-            <div className="space-y-2">
-              <Label htmlFor="accessKey" className="flex items-center gap-2">
-                <Key className="h-4 w-4" />
-                Clave de Acceso (extraída del XML)
-              </Label>
-              <Input
-                id="accessKey"
-                type="text"
-                value={accessKey}
-                disabled
-                className="font-mono bg-background"
-              />
-              <p className="text-xs text-muted-foreground">
-                La clave de acceso fue extraída automáticamente del XML
-              </p>
-            </div>
+            </Dropzone>
+          </div>
+          <Separator />
 
-            <div className="space-y-2">
-              <Label className="flex items-center gap-2">
-                Ambiente
-              </Label>
-              <div className="flex items-center gap-2">
-                <Badge variant={ambiente === '2' ? 'default' : 'secondary'}>
-                  {ambiente === '1' ? 'Pruebas' : 'Producción'}
-                </Badge>
+          {/* Información del XML (solo lectura) */}
+          {signedXml && accessKey && (
+            <div className="space-y-4 p-4 bg-muted/50 rounded-lg">
+              <div className="space-y-2">
+                <Label htmlFor="accessKey" className="flex items-center gap-2">
+                  <Key className="h-4 w-4" />
+                  Clave de Acceso (extraída del XML)
+                </Label>
+                <Input
+                  id="accessKey"
+                  type="text"
+                  value={accessKey}
+                  disabled
+                  className="font-mono bg-background"
+                />
                 <p className="text-xs text-muted-foreground">
-                  Detectado del XML
+                  La clave de acceso fue extraída automáticamente del XML
                 </p>
               </div>
+
+              <div className="space-y-2">
+                <Label className="flex items-center gap-2">
+                  Ambiente detectado del XML
+                </Label>
+                <div className="flex items-center gap-2">
+                  <Badge variant={ambienteDetectado === '2' ? 'default' : 'secondary'}>
+                    {ambienteDetectado === '1' ? 'Pruebas' : 'Producción'}
+                  </Badge>
+                  <p className="text-xs text-muted-foreground">
+                    Detectado automáticamente del XML
+                  </p>
+                </div>
+                
+                {/* Mostrar advertencia si los ambientes no coinciden */}
+                {ambienteDetectado && ambienteDetectado !== ambiente && (
+                  <div className="mt-2 p-2 bg-yellow-50 border border-yellow-200 rounded-lg">
+                    <div className="flex items-start gap-2">
+                      <div className="w-3 h-3 rounded-full bg-yellow-200 flex items-center justify-center mt-0.5">
+                        <span className="text-yellow-800 text-xs font-bold">!</span>
+                      </div>
+                      <div className="text-xs text-yellow-800">
+                        <p className="font-medium">Atención:</p>
+                        <p>El XML indica ambiente de {ambienteDetectado === '1' ? 'Pruebas' : 'Producción'}, pero estás enviando a ambiente de {ambiente === '1' ? 'Pruebas' : 'Producción'}.</p>
+                        <p>Verifica que esta selección sea correcta.</p>
+                      </div>
+                    </div>
+                  </div>
+                )}
+              </div>
             </div>
-          </div>
-        )}
-      </CardContent>
-    </Card>
+          )}
+        </CardContent>
+      </Card>
+
+      {/* Selector de Servidor para Envío */}
+      <ServidorSelector
+        ambiente={ambiente}
+        onAmbienteChange={onAmbienteChange}
+        disabled={isLoading}
+      />
+    </div>
   )
 }

+ 141 - 0
src/components/envio-sri/ServidorSelector.tsx

@@ -0,0 +1,141 @@
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
+import { Label } from "@/components/ui/label"
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
+import { Badge } from "@/components/ui/badge"
+import { Server, Globe } from "lucide-react"
+
+interface ServidorSelectorProps {
+  ambiente: '1' | '2'
+  onAmbienteChange: (ambiente: '1' | '2') => void
+  disabled?: boolean
+}
+
+export function ServidorSelector({
+  ambiente,
+  onAmbienteChange,
+  disabled = false,
+}: ServidorSelectorProps) {
+  const getServerInfo = () => {
+    return ambiente === '2' 
+      ? { 
+          url: "https://cel.sri.gob.ec", 
+          nombre: "Producción", 
+          descripcion: "Servidor oficial para facturas reales"
+        }
+      : { 
+          url: "https://celcer.sri.gob.ec", 
+          nombre: "Pruebas", 
+          descripcion: "Servidor de pruebas para testing"
+        }
+  }
+
+  const serverInfo = getServerInfo()
+
+  return (
+    <Card>
+      <CardHeader>
+        <CardTitle className="flex items-center gap-2">
+          <Server className="h-5 w-5" />
+          Selección de Servidor SRI
+        </CardTitle>
+        <CardDescription>
+          Elige el servidor SRI al que deseas enviar el comprobante
+        </CardDescription>
+      </CardHeader>
+      <CardContent className="space-y-4">
+        {/* Selector de ambiente */}
+        <div className="space-y-2">
+          <Label htmlFor="ambiente-selector">Servidor SRI</Label>
+          <Select 
+            value={ambiente} 
+            onValueChange={(value: '1' | '2') => onAmbienteChange(value)}
+            disabled={disabled}
+          >
+            <SelectTrigger id="ambiente-selector">
+              <SelectValue placeholder="Selecciona el servidor" />
+            </SelectTrigger>
+            <SelectContent>
+              <SelectItem value="1">
+                <div className="flex items-center gap-2">
+                  <Badge variant="secondary" className="text-xs">Pruebas</Badge>
+                  <span>Servidor de Pruebas</span>
+                </div>
+              </SelectItem>
+              <SelectItem value="2">
+                <div className="flex items-center gap-2">
+                  <Badge variant="default" className="text-xs">Producción</Badge>
+                  <span>Servidor de Producción</span>
+                </div>
+              </SelectItem>
+            </SelectContent>
+          </Select>
+          <p className="text-xs text-muted-foreground">
+            Selecciona el servidor según el tipo de comprobante que vas a enviar
+          </p>
+        </div>
+
+        {/* Información del servidor seleccionado */}
+        <div className="space-y-2 p-3 bg-muted/30 rounded-lg">
+          <div className="flex items-center gap-2">
+            <Globe className="h-4 w-4" />
+            <Label className="text-sm font-medium">Servidor seleccionado:</Label>
+          </div>
+          
+          <div className="space-y-2">
+            <div className="flex items-center gap-2">
+              <Badge variant={ambiente === '2' ? 'default' : 'secondary'} className="text-xs">
+                {serverInfo.nombre}
+              </Badge>
+            </div>
+            <div className="text-sm font-mono bg-background p-2 rounded border">
+              {serverInfo.url}
+            </div>
+            <p className="text-xs text-muted-foreground">
+              {serverInfo.descripcion}
+            </p>
+          </div>
+        </div>
+
+        {/* Advertencia sobre producción */}
+        {ambiente === '2' && (
+          <div className="p-3 bg-red-50 border border-red-200 rounded-lg">
+            <div className="flex items-start gap-2">
+              <div className="w-4 h-4 rounded-full bg-red-200 flex items-center justify-center mt-0.5">
+                <span className="text-red-800 text-xs font-bold">!</span>
+              </div>
+              <div className="text-xs text-red-800">
+                <p className="font-medium mb-1">Atención:</p>
+                <ul className="list-disc list-inside space-y-1">
+                  <li>Estás usando el servidor de producción oficial del SRI</li>
+                  <li>Los comprobantes enviados serán válidos legalmente</li>
+                  <li>Asegúrate de que los datos del comprobante sean correctos</li>
+                  <li>Verifica que tengas la configuración tributaria adecuada</li>
+                </ul>
+              </div>
+            </div>
+          </div>
+        )}
+
+        {/* Nota sobre pruebas */}
+        {ambiente === '1' && (
+          <div className="p-3 bg-blue-50 border border-blue-200 rounded-lg">
+            <div className="flex items-start gap-2">
+              <div className="w-4 h-4 rounded-full bg-blue-200 flex items-center justify-center mt-0.5">
+                <span className="text-blue-800 text-xs font-bold">i</span>
+              </div>
+              <div className="text-xs text-blue-800">
+                <p className="font-medium mb-1">Nota:</p>
+                <ul className="list-disc list-inside space-y-1">
+                  <li>Estás usando el servidor de pruebas del SRI</li>
+                  <li>Los comprobantes enviados no tendrán validez legal</li>
+                  <li>Ideal para testing y validación de formatos</li>
+                  <li>No genera registros fiscales oficiales</li>
+                </ul>
+              </div>
+            </div>
+          </div>
+        )}
+      </CardContent>
+    </Card>
+  )
+}

+ 1 - 19
src/components/firmar/SignActions.tsx

@@ -1,4 +1,4 @@
-import { Loader2, Download } from "lucide-react"
+import { Loader2 } from "lucide-react"
 import { Button } from "@/components/ui/button"
 
 interface SignActionsProps {
@@ -6,10 +6,7 @@ interface SignActionsProps {
   p12File: File | null
   password: string
   isLoading: boolean
-  signedXml: string | null
   onSign: () => void
-  onDownload: () => void
-  onReset: () => void
 }
 
 export function SignActions({
@@ -17,10 +14,7 @@ export function SignActions({
   p12File,
   password,
   isLoading,
-  signedXml,
   onSign,
-  onDownload,
-  onReset,
 }: SignActionsProps) {
   return (
     <div className="flex gap-4">
@@ -38,18 +32,6 @@ export function SignActions({
           "Firmar XML"
         )}
       </Button>
-
-      {signedXml && (
-        <>
-          <Button onClick={onDownload} variant="outline">
-            <Download className="mr-2 h-4 w-4" />
-            Descargar XML Firmado
-          </Button>
-          <Button onClick={onReset} variant="ghost">
-            Limpiar
-          </Button>
-        </>
-      )}
     </div>
   )
 }

+ 32 - 6
src/hooks/envio-sri/useEnvioSRI.ts

@@ -9,7 +9,12 @@ export function useEnvioSRI() {
   const [password, setPassword] = useState("")
   const [signedXml, setSignedXml] = useState<string | null>(null)
   const [accessKey, setAccessKey] = useState<string>("")
-  const [ambiente, setAmbiente] = useState<'1' | '2'>('1') // 1=Pruebas, 2=Producción
+  
+  // Ambiente detectado del XML (solo lectura, basado en el XML cargado)
+  const [ambienteDetectado, setAmbienteDetectado] = useState<'1' | '2' | null>(null)
+  
+  // Ambiente seleccionado para envío (puede ser diferente al detectado)
+  const [ambienteEnvio, setAmbienteEnvio] = useState<'1' | '2'>('1') // 1=Pruebas, 2=Producción
 
   const [isLoading, setIsLoading] = useState(false)
   const [estadoEnvio, setEstadoEnvio] = useState<EstadoEnvio>("idle")
@@ -70,12 +75,17 @@ export function useEnvioSRI() {
     setEstadoEnvio("enviando")
 
     try {
+      const requestBody: any = { signedXml }
+      
+      // Enviar el ambiente seleccionado para envío (no el detectado del XML)
+      requestBody.ambiente = ambienteEnvio === '2' ? 'produccion' : 'pruebas'
+
       const response = await fetch("/api/send-to-sri", {
         method: "POST",
         headers: {
           "Content-Type": "application/json",
         },
-        body: JSON.stringify({ signedXml }),
+        body: JSON.stringify(requestBody),
       })
 
       const data = await response.json()
@@ -127,12 +137,17 @@ export function useEnvioSRI() {
     setEstadoEnvio("verificando")
 
     try {
+      const requestBody: any = { accessKey }
+      
+      // Usar el ambiente seleccionado para envío (no el detectado del XML)
+      requestBody.ambiente = ambienteEnvio === '2' ? 'produccion' : 'pruebas'
+
       const response = await fetch("/api/poll-sri", {
         method: "POST",
         headers: {
           "Content-Type": "application/json",
         },
-        body: JSON.stringify({ accessKey }),
+        body: JSON.stringify(requestBody),
       })
 
       const data: RespuestaVerificacionSRI = await response.json()
@@ -186,9 +201,16 @@ export function useEnvioSRI() {
       setXmlFile(file)
       setAccessKey(info.claveAcceso)
 
-      // Establecer el ambiente si se encuentra en el XML
+      // Establecer el ambiente detectado del XML (solo lectura)
       if (info.ambiente) {
-        setAmbiente(info.ambiente)
+        setAmbienteDetectado(info.ambiente)
+        
+        // Si es la primera vez que carga un XML, sincronizar el ambiente de envío
+        // pero solo si el usuario no ha cambiado manualmente el ambiente de envío
+        if (!ambienteDetectado && ambienteEnvio === '1') {
+          setAmbienteEnvio(info.ambiente)
+          toast.info(`Ambiente detectado del XML: ${info.ambiente === '2' ? 'Producción' : 'Pruebas'}`)
+        }
       }
 
       toast.success(`XML cargado: ${file.name}`)
@@ -237,6 +259,8 @@ export function useEnvioSRI() {
     setPassword("")
     setSignedXml(null)
     setAccessKey("")
+    setAmbienteDetectado(null)
+    setAmbienteEnvio('1')
     setEstadoEnvio("idle")
     setRespuestaAutorizacion(null)
   }
@@ -248,7 +272,8 @@ export function useEnvioSRI() {
     password,
     signedXml,
     accessKey,
-    ambiente,
+    ambienteDetectado,
+    ambienteEnvio,
     isLoading,
     estadoEnvio,
     respuestaAutorizacion,
@@ -258,6 +283,7 @@ export function useEnvioSRI() {
     setP12File,
     setPassword,
     setAccessKey,
+    setAmbienteEnvio,
 
     // Actions
     handleSign,

+ 3 - 1
src/hooks/firmar/useXmlSigning.ts

@@ -11,7 +11,7 @@ export function useXmlSigning() {
   const handleSign = async () => {
     if (!xmlFile || !p12File || !password) {
       toast.error("Por favor completa todos los campos")
-      return
+      return false
     }
 
     setIsLoading(true)
@@ -36,9 +36,11 @@ export function useXmlSigning() {
 
       setSignedXml(data.signedXml)
       toast.success("XML firmado exitosamente")
+      return true
     } catch (error) {
       console.error(error)
       toast.error(error instanceof Error ? error.message : "Error al firmar el XML")
+      return false
     } finally {
       setIsLoading(false)
     }