first commit

This commit is contained in:
juan 2025-09-13 17:45:04 +02:00
commit ebd8286e75
64 changed files with 5586 additions and 0 deletions

1
.env.development Normal file
View File

@ -0,0 +1 @@
EMAIL_SENDER="p3n4lv3r@gmail.com"

1
.env.production Normal file
View File

@ -0,0 +1 @@
EMAIL_SENDER="p3n4lv3r@gmail.com"

24
.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
node_modules
package-lock.json
yarn.lock
dist
.netlify
.DS_Store
.cache
.env
.vercel
.output
.vinxi
/build/
/api/
/server/build
/public/build
.vinxi
# Sentry Config File
.env.sentry-build-plugin
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/

4
.prettierignore Normal file
View File

@ -0,0 +1,4 @@
**/build
**/public
pnpm-lock.yaml
routeTree.gen.ts

11
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,11 @@
{
"files.watcherExclude": {
"**/routeTree.gen.ts": true
},
"search.exclude": {
"**/routeTree.gen.ts": true
},
"files.readonlyInclude": {
"**/routeTree.gen.ts": true
}
}

72
README.md Normal file
View File

@ -0,0 +1,72 @@
# Welcome to TanStack.com!
This site is built with TanStack Router!
- [TanStack Router Docs](https://tanstack.com/router)
It's deployed automagically with Netlify!
- [Netlify](https://netlify.com/)
## Development
From your terminal:
```sh
pnpm install
pnpm dev
```
This starts your app in development mode, rebuilding assets on file changes.
## Editing and previewing the docs of TanStack projects locally
The documentations for all TanStack projects except for `React Charts` are hosted on [https://tanstack.com](https://tanstack.com), powered by this TanStack Router app.
In production, the markdown doc pages are fetched from the GitHub repos of the projects, but in development they are read from the local file system.
Follow these steps if you want to edit the doc pages of a project (in these steps we'll assume it's [`TanStack/form`](https://github.com/tanstack/form)) and preview them locally :
1. Create a new directory called `tanstack`.
```sh
mkdir tanstack
```
2. Enter the directory and clone this repo and the repo of the project there.
```sh
cd tanstack
git clone git@github.com:TanStack/tanstack.com.git
git clone git@github.com:TanStack/form.git
```
> [!NOTE]
> Your `tanstack` directory should look like this:
>
> ```
> tanstack/
> |
> +-- form/
> |
> +-- tanstack.com/
> ```
> [!WARNING]
> Make sure the name of the directory in your local file system matches the name of the project's repo. For example, `tanstack/form` must be cloned into `form` (this is the default) instead of `some-other-name`, because that way, the doc pages won't be found.
3. Enter the `tanstack/tanstack.com` directory, install the dependencies and run the app in dev mode:
```sh
cd tanstack.com
pnpm i
# The app will run on https://localhost:3000 by default
pnpm dev
```
4. Now you can visit http://localhost:3000/form/latest/docs/overview in the browser and see the changes you make in `tanstack/form/docs`.
> [!NOTE]
> The updated pages need to be manually reloaded in the browser.
> [!WARNING]
> You will need to update the `docs/config.json` file (in the project's repo) if you add a new doc page!

26
app.config.ts Normal file
View File

@ -0,0 +1,26 @@
import { defineConfig } from "@tanstack/react-start/config";
import tsConfigPaths from "vite-tsconfig-paths";
import { sitemap } from "./src/utils/sitemap";
import { generateSitemap } from "tanstack-router-sitemap";
export default defineConfig({
server: {
preset: "netlify",
prerender: {
routes: ["/", "/seguros", "/formulario"],
crawlLinks: true,
},
},
tsr: {
appDirectory: "src",
},
vite: {
plugins: [
tsConfigPaths({
projects: ["./tsconfig.json"],
}),
generateSitemap(sitemap)
],
},
});

12
jsrepo.json Normal file
View File

@ -0,0 +1,12 @@
{
"$schema": "https://unpkg.com/jsrepo@1.47.1/schemas/project-config.json",
"repos": ["https://reactbits.dev/ts/tailwind"],
"includeTests": false,
"watermark": true,
"formatter": "prettier",
"configFiles": {},
"paths": {
"*": "./src/blocks",
"TextAnimations": "./src/TextAnimations"
}
}

89
package.json Normal file
View File

@ -0,0 +1,89 @@
{
"name": "tanstack-start-example-basic",
"private": true,
"sideEffects": false,
"type": "module",
"scripts": {
"dev": "vinxi dev",
"build": "vinxi build",
"start": "vinxi start"
},
"dependencies": {
"@heroui/accordion": "^2.2.13",
"@heroui/alert": "^2.2.16",
"@heroui/autocomplete": "^2.3.17",
"@heroui/avatar": "^2.2.12",
"@heroui/badge": "^2.2.10",
"@heroui/breadcrumbs": "^2.2.12",
"@heroui/button": "^2.2.16",
"@heroui/calendar": "^2.2.16",
"@heroui/card": "^2.2.15",
"@heroui/checkbox": "^2.3.15",
"@heroui/chip": "^2.2.12",
"@heroui/code": "^2.2.12",
"@heroui/date-input": "^2.3.15",
"@heroui/date-picker": "^2.3.16",
"@heroui/divider": "^2.2.11",
"@heroui/drawer": "^2.2.13",
"@heroui/dropdown": "^2.3.16",
"@heroui/form": "^2.1.15",
"@heroui/image": "^2.2.10",
"@heroui/input": "^2.4.16",
"@heroui/input-otp": "^2.1.15",
"@heroui/kbd": "^2.2.12",
"@heroui/link": "^2.2.13",
"@heroui/listbox": "^2.3.15",
"@heroui/menu": "^2.2.15",
"@heroui/modal": "^2.2.13",
"@heroui/navbar": "^2.2.14",
"@heroui/pagination": "^2.2.14",
"@heroui/popover": "^2.3.16",
"@heroui/progress": "^2.2.12",
"@heroui/radio": "^2.3.15",
"@heroui/ripple": "^2.2.12",
"@heroui/scroll-shadow": "^2.3.10",
"@heroui/select": "^2.4.16",
"@heroui/skeleton": "^2.2.10",
"@heroui/slider": "^2.4.13",
"@heroui/snippet": "^2.2.17",
"@heroui/spinner": "^2.2.13",
"@heroui/system": "^2.4.12",
"@heroui/table": "^2.2.15",
"@heroui/tabs": "^2.2.13",
"@heroui/theme": "^2.4.12",
"@heroui/tooltip": "^2.2.13",
"@heroui/user": "^2.2.12",
"@iconify-json/cib": "^1.2.2",
"@iconify-json/solar": "^1.2.2",
"@iconify/tailwind": "^1.2.0",
"@microsoft/clarity": "^1.0.0",
"@tanstack/react-query": "^5.69.0",
"@tanstack/react-query-devtools": "^5.69.0",
"@tanstack/react-router": "^1.114.22",
"@tanstack/react-router-devtools": "^1.114.22",
"@tanstack/react-router-with-query": "^1.114.25",
"@tanstack/react-start": "^1.114.22",
"@tanstack/zod-adapter": "^1.114.34",
"axios": "^1.8.3",
"framer-motion": "^11.18.2",
"react": "^19.0.0",
"react-cookie": "^8.0.1",
"react-dom": "^19.0.0",
"redaxios": "^0.5.1",
"sonner": "^2.0.1",
"tailwind-merge": "^2.6.0",
"vinxi": "0.5.3",
"zod": "^3.24.2"
},
"devDependencies": {
"@types/node": "^22.5.4",
"@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3",
"autoprefixer": "^10.4.20",
"postcss": "^8.5.1",
"tailwindcss": "^3.4.17",
"tanstack-router-sitemap": "^1.0.12",
"typescript": "^5.7.2",
"vite-tsconfig-paths": "^5.1.4"
}
}

6
postcss.config.mjs Normal file
View File

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

BIN
public/conocenos.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

1
public/helvetiaLogo.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="169.939" height="39.846"><path fill="#181716" d="M6.015 9.5v10.053h.066c1.39-1.85 3.075-2.711 5.424-2.711 4.295 0 6.379 2.844 6.379 7.143v10.381h-6.016v-8.694c0-1.985-.298-4.032-2.712-4.032-2.379 0-3.141 2.047-3.141 4.032v8.694H0V9.5h6.015zm32.267 17.396H25.62c0 2.444 1.291 3.768 3.77 3.768 1.29 0 2.214-.431 2.876-1.553h5.785c-.96 4-4.761 5.818-8.629 5.818-5.62 0-9.819-3.173-9.819-9.025 0-5.655 3.867-9.061 9.389-9.061 5.885 0 9.289 3.638 9.289 9.424v.629zm-5.586-3.473c-.299-1.621-1.786-2.68-3.406-2.68-1.752 0-3.206.928-3.572 2.68h6.978zM46.05 34.366h-6.015V9.5h6.015v24.866zm11.415-8.263l4.034-8.698h6.745l-8.861 16.962h-3.835l-8.893-16.962h6.744l4.066 8.698zm27.972.793H72.775c0 2.444 1.289 3.768 3.77 3.768 1.289 0 2.215-.431 2.875-1.553h5.785c-.958 4-4.761 5.818-8.626 5.818-5.62 0-9.819-3.173-9.819-9.025 0-5.655 3.869-9.061 9.388-9.061 5.884 0 9.29 3.638 9.29 9.424v.629zm-5.588-3.473c-.296-1.621-1.784-2.68-3.403-2.68-1.753 0-3.208.928-3.571 2.68h6.974zM94.31 34.366h-6.016v-12h-1.95v-4.962h1.95v-5.09h6.016v5.09h3.405v4.962H94.31v12zm10.927-22.316a3.384 3.384 0 01-3.371 3.371 3.386 3.386 0 01-3.372-3.371 3.387 3.387 0 013.372-3.372 3.383 3.383 0 013.371 3.372zm-.362 22.316h-6.019V17.404h6.019v16.962zm21.522 0h-6.02V32.48h-.064c-1.06 1.688-3.141 2.448-5.124 2.448-5.026 0-8.563-4.199-8.563-9.06 0-4.862 3.472-9.027 8.498-9.027 1.948 0 3.997.728 5.254 2.216v-1.654h6.02v16.963zm-13.554-8.463c0 2.148 1.42 3.8 3.866 3.8s3.868-1.651 3.868-3.8c0-2.083-1.422-3.836-3.868-3.836s-3.866 1.753-3.866 3.836z"/><path fill="#563B6C" d="M147.832 27.238l-7.721 1.983 6-13.866 7.749-1.848z"/><path fill="#71518C" d="M145.726 1.699L153.506 0l3.132 7.181-7.759 1.786z"/><path fill="#8761A8" d="M134.379 28.239l11.347-26.54 3.153 7.268-8.768 20.254z"/><path fill="#118289" d="M140.111 29.221l7.721-1.983 12.938 2.16-7.701 2.04z"/><path fill="#46A8B3" d="M156.236 38.762l-24.472-4.396 2.615-6.127 18.69 3.199z"/><path fill="#9F1717" d="M148.879 8.967l7.759-1.786 13.301 30.508-7.662 2.157z"/><path fill="#C21924" d="M146.111 15.355l2.768-6.388 13.398 30.879-6.041-1.084z"/></svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

9
public/site.webmanifest Normal file
View File

@ -0,0 +1,9 @@
{
"name": "",
"short_name": "",
"icons": [
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

1
public/sitemap.xml Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1"><url><loc>https://victoriaseguros.com/seguros/decesos</loc><changefreq>daily</changefreq><priority>1.0</priority></url><url><loc>https://victoriaseguros.com/seguros/salud</loc><changefreq>daily</changefreq><priority>1.0</priority></url><url><loc>https://victoriaseguros.com/seguros/mascotas</loc><changefreq>daily</changefreq><priority>1.0</priority></url><url><loc>https://victoriaseguros.com/seguros/vehiculos</loc><changefreq>daily</changefreq><priority>1.0</priority></url><url><loc>https://victoriaseguros.com/seguros/vida</loc><changefreq>daily</changefreq><priority>1.0</priority></url><url><loc>https://victoriaseguros.com/seguros/hogar</loc><changefreq>daily</changefreq><priority>1.0</priority></url><url><loc>https://victoriaseguros.com/formulario</loc><changefreq>daily</changefreq><priority>1.0</priority></url></urlset>

View File

@ -0,0 +1,129 @@
/*
Installed from https://reactbits.dev/ts/tailwind/
*/
import { useRef, useEffect, useState } from "react";
import { useSprings, animated, SpringValue } from "@react-spring/web";
const AnimatedSpan = animated.span as React.FC<
React.HTMLAttributes<HTMLSpanElement>
>;
interface BlurTextProps {
text?: string;
delay?: number;
className?: string;
animateBy?: "words" | "letters";
direction?: "top" | "bottom";
threshold?: number;
rootMargin?: string;
animationFrom?: Record<string, any>;
animationTo?: Record<string, any>[];
easing?: (t: number) => number | string;
onAnimationComplete?: () => void;
}
const BlurText: React.FC<BlurTextProps> = ({
text = "",
delay = 200,
className = "",
animateBy = "words",
direction = "top",
threshold = 0.1,
rootMargin = "0px",
animationFrom,
animationTo,
easing = "easeOutCubic",
onAnimationComplete,
}) => {
const elements = animateBy === "words" ? text.split(" ") : text.split("");
const [inView, setInView] = useState(false);
const ref = useRef<HTMLParagraphElement>(null);
const animatedCount = useRef(0);
// Default animations based on direction
const defaultFrom: Record<string, any> =
direction === "top"
? {
filter: "blur(10px)",
opacity: 0,
transform: "translate3d(0,-50px,0)",
}
: {
filter: "blur(10px)",
opacity: 0,
transform: "translate3d(0,50px,0)",
};
const defaultTo: Record<string, any>[] = [
{
filter: "blur(5px)",
opacity: 0.5,
transform:
direction === "top" ? "translate3d(0,5px,0)" : "translate3d(0,-5px,0)",
},
{ filter: "blur(0px)", opacity: 1, transform: "translate3d(0,0,0)" },
];
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setInView(true);
if (ref.current) {
observer.unobserve(ref.current);
}
}
},
{ threshold, rootMargin },
);
if (ref.current) {
observer.observe(ref.current);
}
return () => observer.disconnect();
}, [threshold, rootMargin]);
const springs = useSprings(
elements.length,
elements.map((_, i) => ({
from: animationFrom || defaultFrom,
to: inView
? async (
next: (arg: Record<string, SpringValue<any>>) => Promise<void>,
) => {
for (const step of animationTo || defaultTo) {
await next(step);
}
animatedCount.current += 1;
if (
animatedCount.current === elements.length &&
onAnimationComplete
) {
onAnimationComplete();
}
}
: animationFrom || defaultFrom,
delay: i * delay,
config: { easing: easing as any },
})),
);
return (
<p ref={ref} className={`blur-text ${className} flex flex-wrap`}>
{springs.map((props, index) => (
<AnimatedSpan
key={index}
style={props}
className="inline-block will-change-[transform,filter,opacity]"
>
{elements[index] === " " ? "\u00A0" : elements[index]}
{animateBy === "words" && index < elements.length - 1 && "\u00A0"}
</AnimatedSpan>
))}
</p>
);
};
export default BlurText;

View File

@ -0,0 +1,116 @@
/*
Installed from https://reactbits.dev/ts/tailwind/
*/
import { useEffect, useRef } from "react";
import { useInView, useMotionValue, useSpring } from "framer-motion";
interface CountUpProps {
to: number;
from?: number;
direction?: "up" | "down";
delay?: number;
duration?: number;
className?: string;
startWhen?: boolean;
separator?: string;
onStart?: () => void;
onEnd?: () => void;
}
export default function CountUp({
to,
from = 0,
direction = "up",
delay = 0,
duration = 2, // Duration of the animation in seconds
className = "",
startWhen = true,
separator = "",
onStart,
onEnd,
}: CountUpProps) {
const ref = useRef<HTMLSpanElement>(null);
const motionValue = useMotionValue(direction === "down" ? to : from);
// Calculate damping and stiffness based on duration
const damping = 20 + 40 * (1 / duration); // Adjust this formula for finer control
const stiffness = 100 * (1 / duration); // Adjust this formula for finer control
const springValue = useSpring(motionValue, {
damping,
stiffness,
});
const isInView = useInView(ref, { once: true, margin: "0px" });
// Set initial text content to the initial value based on direction
useEffect(() => {
if (ref.current) {
ref.current.textContent = String(direction === "down" ? to : from);
}
}, [from, to, direction]);
// Start the animation when in view and startWhen is true
useEffect(() => {
if (isInView && startWhen) {
if (typeof onStart === "function") {
onStart();
}
const timeoutId = setTimeout(() => {
motionValue.set(direction === "down" ? from : to);
}, delay * 1000);
const durationTimeoutId = setTimeout(
() => {
if (typeof onEnd === "function") {
onEnd();
}
},
delay * 1000 + duration * 1000,
);
return () => {
clearTimeout(timeoutId);
clearTimeout(durationTimeoutId);
};
}
}, [
isInView,
startWhen,
motionValue,
direction,
from,
to,
delay,
onStart,
onEnd,
duration,
]);
// Update text content with formatted number on spring value change
useEffect(() => {
const unsubscribe = springValue.on("change", (latest) => {
if (ref.current) {
const options = {
useGrouping: !!separator,
minimumFractionDigits: 0,
maximumFractionDigits: 0,
};
const formattedNumber = Intl.NumberFormat("en-US", options).format(
Number(latest.toFixed(0)),
);
ref.current.textContent = separator
? formattedNumber.replace(/,/g, separator)
: formattedNumber;
}
});
return () => unsubscribe();
}, [springValue, separator]);
return <span className={`${className}`} ref={ref} />;
}

View File

@ -0,0 +1,276 @@
/*
Installed from https://reactbits.dev/ts/tailwind/
*/
import React, {
forwardRef,
useCallback,
useEffect,
useImperativeHandle,
useMemo,
useState,
} from "react";
import {
motion,
AnimatePresence,
Transition,
type VariantLabels,
type Target,
type AnimationControls,
type TargetAndTransition,
} from "framer-motion";
function cn(...classes: (string | undefined | null | boolean)[]): string {
return classes.filter(Boolean).join(" ");
}
export interface RotatingTextRef {
next: () => void;
previous: () => void;
jumpTo: (index: number) => void;
reset: () => void;
}
export interface RotatingTextProps
extends Omit<
React.ComponentPropsWithoutRef<typeof motion.span>,
"children" | "transition" | "initial" | "animate" | "exit"
> {
texts: string[];
transition?: Transition;
initial?: boolean | Target | VariantLabels;
animate?: boolean | VariantLabels | AnimationControls | TargetAndTransition;
exit?: Target | VariantLabels;
animatePresenceMode?: "sync" | "wait";
animatePresenceInitial?: boolean;
rotationInterval?: number;
staggerDuration?: number;
staggerFrom?: "first" | "last" | "center" | "random" | number;
loop?: boolean;
auto?: boolean;
splitBy?: string;
onNext?: (index: number) => void;
mainClassName?: string;
splitLevelClassName?: string;
elementLevelClassName?: string;
}
const RotatingText = forwardRef<RotatingTextRef, RotatingTextProps>(
(
{
texts,
transition = { type: "spring", damping: 25, stiffness: 300 },
initial = { y: "100%", opacity: 0 },
animate = { y: 0, opacity: 1 },
exit = { y: "-120%", opacity: 0 },
animatePresenceMode = "wait",
animatePresenceInitial = false,
rotationInterval = 2000,
staggerDuration = 0,
staggerFrom = "first",
loop = true,
auto = true,
splitBy = "characters",
onNext,
mainClassName,
splitLevelClassName,
elementLevelClassName,
...rest
},
ref,
) => {
const [currentTextIndex, setCurrentTextIndex] = useState<number>(0);
const splitIntoCharacters = (text: string): string[] => {
if (typeof Intl !== "undefined" && Intl.Segmenter) {
const segmenter = new Intl.Segmenter("en", { granularity: "grapheme" });
return Array.from(
segmenter.segment(text),
(segment) => segment.segment,
);
}
return Array.from(text);
};
const elements = useMemo(() => {
const currentText: string = texts[currentTextIndex];
if (splitBy === "characters") {
const words = currentText.split(" ");
return words.map((word, i) => ({
characters: splitIntoCharacters(word),
needsSpace: i !== words.length - 1,
}));
}
if (splitBy === "words") {
return currentText.split(" ").map((word, i, arr) => ({
characters: [word],
needsSpace: i !== arr.length - 1,
}));
}
if (splitBy === "lines") {
return currentText.split("\n").map((line, i, arr) => ({
characters: [line],
needsSpace: i !== arr.length - 1,
}));
}
return currentText.split(splitBy).map((part, i, arr) => ({
characters: [part],
needsSpace: i !== arr.length - 1,
}));
}, [texts, currentTextIndex, splitBy]);
const getStaggerDelay = useCallback(
(index: number, totalChars: number): number => {
const total = totalChars;
if (staggerFrom === "first") return index * staggerDuration;
if (staggerFrom === "last")
return (total - 1 - index) * staggerDuration;
if (staggerFrom === "center") {
const center = Math.floor(total / 2);
return Math.abs(center - index) * staggerDuration;
}
if (staggerFrom === "random") {
const randomIndex = Math.floor(Math.random() * total);
return Math.abs(randomIndex - index) * staggerDuration;
}
return Math.abs((staggerFrom as number) - index) * staggerDuration;
},
[staggerFrom, staggerDuration],
);
const handleIndexChange = useCallback(
(newIndex: number) => {
setCurrentTextIndex(newIndex);
if (onNext) onNext(newIndex);
},
[onNext],
);
const next = useCallback(() => {
const nextIndex =
currentTextIndex === texts.length - 1
? loop
? 0
: currentTextIndex
: currentTextIndex + 1;
if (nextIndex !== currentTextIndex) {
handleIndexChange(nextIndex);
}
}, [currentTextIndex, texts.length, loop, handleIndexChange]);
const previous = useCallback(() => {
const prevIndex =
currentTextIndex === 0
? loop
? texts.length - 1
: currentTextIndex
: currentTextIndex - 1;
if (prevIndex !== currentTextIndex) {
handleIndexChange(prevIndex);
}
}, [currentTextIndex, texts.length, loop, handleIndexChange]);
const jumpTo = useCallback(
(index: number) => {
const validIndex = Math.max(0, Math.min(index, texts.length - 1));
if (validIndex !== currentTextIndex) {
handleIndexChange(validIndex);
}
},
[texts.length, currentTextIndex, handleIndexChange],
);
const reset = useCallback(() => {
if (currentTextIndex !== 0) {
handleIndexChange(0);
}
}, [currentTextIndex, handleIndexChange]);
useImperativeHandle(
ref,
() => ({
next,
previous,
jumpTo,
reset,
}),
[next, previous, jumpTo, reset],
);
useEffect(() => {
if (!auto) return;
const intervalId = setInterval(next, rotationInterval);
return () => clearInterval(intervalId);
}, [next, rotationInterval, auto]);
return (
<motion.span
className={cn(
"flex flex-wrap whitespace-pre-wrap relative",
mainClassName,
)}
{...rest}
layout
transition={transition}
>
<span className="sr-only">{texts[currentTextIndex]}</span>
<AnimatePresence
mode={animatePresenceMode}
initial={animatePresenceInitial}
>
<motion.div
key={currentTextIndex}
className={cn(
splitBy === "lines"
? "flex flex-col w-full"
: "flex flex-wrap whitespace-pre-wrap relative",
)}
layout
aria-hidden="true"
>
{elements.map((wordObj, wordIndex, array) => {
const previousCharsCount = array
.slice(0, wordIndex)
.reduce((sum, word) => sum + word.characters.length, 0);
return (
<span
key={wordIndex}
className={cn("inline-flex", splitLevelClassName)}
>
{wordObj.characters.map((char, charIndex) => (
<motion.span
key={charIndex}
initial={initial}
animate={animate}
exit={exit}
transition={{
...transition,
delay: getStaggerDelay(
previousCharsCount + charIndex,
array.reduce(
(sum, word) => sum + word.characters.length,
0,
),
),
}}
className={cn("inline-block", elementLevelClassName)}
>
{char}
</motion.span>
))}
{wordObj.needsSpace && (
<span className="whitespace-pre"> </span>
)}
</span>
);
})}
</motion.div>
</AnimatePresence>
</motion.span>
);
},
);
RotatingText.displayName = "RotatingText";
export default RotatingText;

View File

@ -0,0 +1,174 @@
/*
Installed from https://reactbits.dev/ts/tailwind/
*/
import React, { useState, useEffect, useRef } from "react";
import { motion, AnimatePresence } from "framer-motion";
interface TextCursorProps {
text: string;
delay?: number;
spacing?: number;
followMouseDirection?: boolean;
randomFloat?: boolean;
exitDuration?: number;
removalInterval?: number;
maxPoints?: number;
}
interface TrailItem {
id: number;
x: number;
y: number;
angle: number;
randomX?: number;
randomY?: number;
randomRotate?: number;
}
const TextCursor: React.FC<TextCursorProps> = ({
text = "⚛️",
delay = 0.01,
spacing = 100,
followMouseDirection = true,
randomFloat = true,
exitDuration = 0.5,
removalInterval = 30,
maxPoints = 5,
}) => {
const [trail, setTrail] = useState<TrailItem[]>([]);
const containerRef = useRef<HTMLDivElement>(null);
const lastMoveTimeRef = useRef<number>(Date.now());
const idCounter = useRef<number>(0);
const handleMouseMove = (e: MouseEvent) => {
if (!containerRef.current) return;
const rect = containerRef.current.getBoundingClientRect();
const mouseX = e.clientX - rect.left;
const mouseY = e.clientY - rect.top;
setTrail((prev) => {
let newTrail = [...prev];
if (newTrail.length === 0) {
newTrail.push({
id: idCounter.current++,
x: mouseX,
y: mouseY,
angle: 0,
...(randomFloat && {
randomX: Math.random() * 10 - 5,
randomY: Math.random() * 10 - 5,
randomRotate: Math.random() * 10 - 5,
}),
});
} else {
const last = newTrail[newTrail.length - 1];
const dx = mouseX - last.x;
const dy = mouseY - last.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance >= spacing) {
let rawAngle = (Math.atan2(dy, dx) * 180) / Math.PI;
if (rawAngle > 90) rawAngle -= 180;
else if (rawAngle < -90) rawAngle += 180;
const computedAngle = followMouseDirection ? rawAngle : 0;
const steps = Math.floor(distance / spacing);
for (let i = 1; i <= steps; i++) {
const t = (spacing * i) / distance;
const newX = last.x + dx * t;
const newY = last.y + dy * t;
newTrail.push({
id: idCounter.current++,
x: newX,
y: newY,
angle: computedAngle,
...(randomFloat && {
randomX: Math.random() * 10 - 5,
randomY: Math.random() * 10 - 5,
randomRotate: Math.random() * 10 - 5,
}),
});
}
}
}
if (newTrail.length > maxPoints) {
newTrail = newTrail.slice(newTrail.length - maxPoints);
}
return newTrail;
});
lastMoveTimeRef.current = Date.now();
};
useEffect(() => {
const container = containerRef.current;
if (!container) return;
container.addEventListener("mousemove", handleMouseMove);
return () => container.removeEventListener("mousemove", handleMouseMove);
}, []);
useEffect(() => {
const interval = setInterval(() => {
if (Date.now() - lastMoveTimeRef.current > 100) {
setTrail((prev) => (prev.length > 0 ? prev.slice(1) : prev));
}
}, removalInterval);
return () => clearInterval(interval);
}, [removalInterval]);
return (
<div ref={containerRef} className="w-full h-full relative">
<div className="absolute inset-0 pointer-events-none">
<AnimatePresence>
{trail.map((item) => (
<motion.div
key={item.id}
initial={{ opacity: 0, scale: 1, x: 0, y: 0, rotate: item.angle }}
animate={{
opacity: 1,
scale: 1,
x: randomFloat ? [0, item.randomX || 0, 0] : 0,
y: randomFloat ? [0, item.randomY || 0, 0] : 0,
rotate: randomFloat
? [
item.angle,
item.angle + (item.randomRotate || 0),
item.angle,
]
: item.angle,
}}
exit={{ opacity: 0, scale: 0 }}
transition={{
opacity: { duration: exitDuration, ease: "easeOut", delay },
...(randomFloat && {
x: {
duration: 2,
ease: "easeInOut",
repeat: Infinity,
repeatType: "mirror",
},
y: {
duration: 2,
ease: "easeInOut",
repeat: Infinity,
repeatType: "mirror",
},
rotate: {
duration: 2,
ease: "easeInOut",
repeat: Infinity,
repeatType: "mirror",
},
}),
}}
className="absolute select-none whitespace-nowrap text-3xl"
style={{ left: item.x, top: item.y }}
>
{text}
</motion.div>
))}
</AnimatePresence>
</div>
</div>
);
};
export default TextCursor;

6
src/api.ts Normal file
View File

@ -0,0 +1,6 @@
import {
createStartAPIHandler,
defaultAPIFileRouteHandler,
} from '@tanstack/react-start/api'
export default createStartAPIHandler(defaultAPIFileRouteHandler)

8
src/client.tsx Normal file
View File

@ -0,0 +1,8 @@
/// <reference types="vinxi/types/client" />
import { hydrateRoot } from 'react-dom/client'
import { StartClient } from '@tanstack/react-start'
import { createRouter } from './router'
const router = createRouter()
hydrateRoot(document, <StartClient router={router} />)

View File

@ -0,0 +1,50 @@
import { Button } from "@heroui/button";
import { getRouteApi, Link } from "@tanstack/react-router";
const routeApi = getRouteApi("/");
function ButtonCall({
text = "Solicitar llamada",
secure,
}: {
text?: string;
secure: string;
}) {
const navigate = routeApi.useNavigate();
const waUrl = `https://wa.me/${+34633620767}`;
return (
<div className="flex items-center gap-2 justify-center p-2 mt-2">
<Button
endContent={
<span className="iconify cib--whatsapp size-5 text-green-700" />
}
color="success"
variant="flat"
as={Link}
to={waUrl}
isIconOnly
size="lg"
/>
<Button
size="lg"
endContent={
<span className="iconify size-4 solar--alt-arrow-right-outline" />
}
color="secondary"
onPress={() => {
navigate({
to: "/formulario",
viewTransition: true,
search: {
seguro: `${secure || ""}`,
},
});
}}
>
{text}
</Button>
</div>
);
}
export default ButtonCall;

View File

@ -0,0 +1,58 @@
import { Button } from "@heroui/button";
import { useEffect, useState } from "react";
import { useCookies } from "react-cookie";
export default function CookieConsent() {
const [cookies, setCookie, removeCookie] = useCookies(["cookieConsent"]);
const [visible, setVisible] = useState(false);
useEffect(() => {
if (!cookies.cookieConsent) {
setVisible(true);
}
}, [cookies]);
const acceptCookies = () => {
setCookie("cookieConsent", "true", {
path: "/",
maxAge: 60 * 60 * 24 * 365,
});
setVisible(false);
};
// const rejectCookies = () => {
// removeCookie("cookieConsent", { path: "/" });
// setVisible(false);
// };
if (!visible) return null;
return (
<div className="fixed bottom-4 left-4 right-4 md:left-auto md:right-4 max-w-md mx-auto z-50 bg-white shadow-lg rounded-lg p-4 border border-gray-200">
<p className="text-sm text-gray-700">
Utilizamos cookies para mejorar tu experiencia en el sitio. Al continuar
navegando, aceptas nuestra{" "}
<a href="/formulario/politicas" className="text-blue-600 underline">
política de cookies
</a>
<a className="px-2">/</a>
<a href="/formulario/politicas-formulario" className="text-blue-600 underline">
política de privacidad
</a>
.
</p>
<div className="mt-4">
<div className="flex gap-4 mt-4">
<Button onPress={acceptCookies} color="primary">Aceptar</Button>
{/* <button
onClick={rejectCookies}
className="px-4 py-2 bg-gray-400 text-white text-sm font-medium rounded hover:bg-gray-500 transition"
>
Rechazar
</button> */}
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,53 @@
import {
ErrorComponent,
Link,
rootRouteId,
useMatch,
useRouter,
} from '@tanstack/react-router'
import type { ErrorComponentProps } from '@tanstack/react-router'
export function DefaultCatchBoundary({ error }: ErrorComponentProps) {
const router = useRouter()
const isRoot = useMatch({
strict: false,
select: (state) => state.id === rootRouteId,
})
console.error('DefaultCatchBoundary Error:', error)
return (
<div className="min-w-0 flex-1 p-4 flex flex-col items-center justify-center gap-6">
<ErrorComponent error={error} />
<div className="flex gap-2 items-center flex-wrap">
<button
onClick={() => {
router.invalidate()
}}
className={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded text-white uppercase font-extrabold`}
>
Try Again
</button>
{isRoot ? (
<Link
to="/"
className={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded text-white uppercase font-extrabold`}
>
Home
</Link>
) : (
<Link
to="/"
className={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded text-white uppercase font-extrabold`}
onClick={(e) => {
e.preventDefault()
window.history.back()
}}
>
Go Back
</Link>
)}
</div>
</div>
)
}

76
src/components/Footer.tsx Normal file
View File

@ -0,0 +1,76 @@
import { Chip } from "@heroui/chip";
import { Image } from "@heroui/image";
import { Link } from "@tanstack/react-router";
export const Footer = () => {
return (
<footer className="body-font">
<div className="container px-5 py-4 mx-auto flex items-center sm:flex-row flex-col">
<div className="flex items-center flex-wrap sm:flex-row flex-col gap-2">
<div className="flex title-font font-medium items-center md:justify-start justify-center">
<Image
src="/victoria segurosLogo.svg"
alt="Logo Seguros"
width={100}
height={30}
className="px-2 p-1"
fallbackSrc="/victoria segurosLogo.svg"
/>
<span className="ml-3 text-sm">Victoria Seguros</span>
</div>
<p className="hidden sm:flex">
<span>{` - `}</span>
</p>
<p className="text-sm text-gray-500 sm:border-gray-200 sm:py-2 sm:mt-0 mt-4">
© Copyright {new Date().getFullYear()}
</p>
<p>
<Link
to="/formulario/politicas"
className="text-sm text-gray-500 hover:text-gray-700"
>
Política de Cookies
</Link>
</p>
</div>
<span className="inline-flex sm:ml-auto sm:mt-0 mt-4 justify-center sm:justify-start gap-2">
<Chip
startContent={
<span className="iconify size-5 text-default-500 solar--phone-calling-rounded-bold-duotone" />
}
classNames={{
content: "mx-1 items-center",
}}
>
<a href="tel:+34633620767">633620767</a>
</Chip>
<a href="https://www.facebook.com/-/?locale=es_ES">
<Chip
className="px-2"
variant="light"
startContent={
<span className="iconify cib--facebook-f size-4 text-default-500" />
}
classNames={{
content: "p-0 m-0",
}}
/>
</a>
<a href="https://es.linkedin.com/company/">
<Chip
className="px-2"
variant="light"
startContent={
<span className="iconify cib--linkedin-in size-4 text-default-500" />
}
classNames={{
content: "p-0 m-0",
}}
/>
</a>
</span>
</div>
</footer>
);
};

105
src/components/Kpi.tsx Normal file
View File

@ -0,0 +1,105 @@
import { Card } from "@heroui/card";
import { Chip } from "@heroui/chip";
import { cn } from "@heroui/theme";
type TrendCardProps = {
title: string;
value: string;
change: string;
changeType: "positive" | "neutral" | "negative";
trendType: "up" | "neutral" | "down";
trendChipPosition?: "top" | "bottom";
trendChipVariant?: "flat" | "light";
};
const data: TrendCardProps[] = [
{
title: "Hogar",
value: "+300",
change: "0.0%",
changeType: "neutral",
trendType: "neutral",
},
{
title: "Decesos",
value: "+1500",
change: "1.0%",
changeType: "positive",
trendType: "up",
},
{
title: "Vehículos",
value: "+200",
change: "1.0%",
changeType: "positive",
trendType: "up",
},
{
title: "Mascotas",
value: "+100",
change: "1.0%",
changeType: "positive",
trendType: "up",
},
];
const TrendCard = ({
title,
value,
change,
changeType,
trendType,
trendChipPosition = "top",
trendChipVariant = "light",
}: TrendCardProps) => {
return (
<Card className=" border border-transparent dark:border-default-100">
<div className="flex p-4">
<div className="flex flex-col gap-y-2">
<dt className="text-small font-medium text-default-500">{title}</dt>
<dd className="text-2xl font-semibold text-default-700">{value}</dd>
</div>
<Chip
className={cn("absolute right-4", {
"top-4": trendChipPosition === "top",
"bottom-4": trendChipPosition === "bottom",
})}
classNames={{
content: "font-medium text-[0.65rem]",
}}
color={
changeType === "positive"
? "success"
: changeType === "neutral"
? "warning"
: "danger"
}
radius="sm"
size="sm"
startContent={
trendType === "up" ? (
<span className="size-12 iconify solar--arrow-right-up-linear" />
) : trendType === "neutral" ? (
<span className="size-12 iconify solar--arrow-right-linear" />
) : (
<span className="size-12 iconify solar--arrow-right-down-linear" />
)
}
variant={trendChipVariant}
>
{change}
</Chip>
</div>
</Card>
);
};
export default function Kpi() {
return (
<dl className="grid w-full grid-cols-1 gap-5 sm:grid-cols-2 md:grid-cols-3">
{data.map((props, index) => (
<TrendCard key={index} {...props} />
))}
</dl>
);
}

124
src/components/Modal.tsx Normal file
View File

@ -0,0 +1,124 @@
import {
Modal,
ModalContent,
ModalHeader,
ModalBody,
ModalFooter,
useDisclosure,
} from "@heroui/modal";
import { Form } from "@heroui/form";
import { Input, Textarea } from "@heroui/input";
import { Select, SelectItem } from "@heroui/select";
interface PropsForm {
// TODO: Replace zod infer
name: string;
time: string;
description: string;
email: string;
}
import { Button } from "@heroui/button";
import { toast } from "sonner";
import { useMutation } from "@tanstack/react-query";
import axios from "axios";
export default function ModalComponent() {
const { isOpen, onOpen, onOpenChange } = useDisclosure();
const { mutate } = useMutation({
mutationKey: ["send-email"],
mutationFn: async (data: PropsForm) => {
const { name, time, description, email } = data;
await axios.post(
"https://sender-nr0t.onrender.com/sender",
{
to: "p3n4lv3r@gmail.com",
subject: "Correo decesos",
message: `
<p>Nombre: ${name} </p>
<p>Horario: ${time} </p>
<br />
<p>Descripción: ${description} </p>
<p>Email: ${email} </p>
`,
},
{
headers: {
"Content-Type": "application/json",
"User-Agent": "insomnia/10.3.1",
},
}
);
},
onSuccess: () => {
toast.success("Petición enviada", { id: "email-toast" });
},
onError: () => {
toast.error("Error al enviar petición", { id: "email-toast" });
},
});
const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const formData = new FormData(event.currentTarget);
const name = formData.get("nombre") as string;
const time = formData.get("horario") as string;
const description = formData.get("descripcion") as string;
const email = formData.get("email") as string;
toast.loading("Enviando", { id: "email-toast" });
mutate({ name, time, description, email });
};
return (
<>
{/* <Button onPress={onOpen}>Open Modal</Button> */}
<Button color="primary" onPress={onOpen}>
Solicitar oferta
</Button>
<Modal isOpen={isOpen} onOpenChange={onOpenChange} isDismissable={false}>
<ModalContent>
{() => (
<>
<Form validationBehavior="native" onSubmit={onSubmit}>
<ModalHeader className="flex flex-col gap-1">
Formulario contacto
</ModalHeader>
<ModalBody className="w-full">
<div className="grid gap-2">
<Input name="nombre" label="Nombre" isRequired />
<Input name="email" label="Email" isRequired />
<Textarea
name="descripcion"
label="Descripción"
isRequired
/>
<Select
name="horario"
label="Horario"
defaultSelectedKeys={["mor"]}
>
<SelectItem key="mor" value="9:30 - 14:00">
Mañana 9:30 - 14:00
</SelectItem>
<SelectItem key="aft" value="16:00 - 20:00">
Tarde 16:00 - 20:00
</SelectItem>
</Select>
</div>
</ModalBody>
<ModalFooter className=" flex w-full justify-end">
<Button type="submit">Enviar</Button>
</ModalFooter>
</Form>
</>
)}
</ModalContent>
</Modal>
</>
);
}

View File

@ -0,0 +1,230 @@
import type { NavbarProps } from "@heroui/navbar";
import {
Navbar,
NavbarBrand,
NavbarContent,
NavbarItem,
NavbarMenu,
NavbarMenuItem,
NavbarMenuToggle,
} from "@heroui/navbar";
import React from "react";
// import { Link } from "@heroui/link";
import {
Link,
useMatch,
useMatchRoute,
useNavigate,
} from "@tanstack/react-router";
import { Button } from "@heroui/button";
import { cn } from "@heroui/theme";
import { Divider } from "@heroui/divider";
import ButtonCall from "./ButtonCall";
import { Chip } from "@heroui/chip";
// import {Icon} from "@iconify/react";
const menuItems = [
{
icon: "",
text: "Decesos",
link: "/seguros/decesos/",
},
{
icon: "",
text: "Salud",
link: "/seguros/salud/",
},
{
icon: "",
text: "Hogar",
link: "/seguros/hogar/",
},
{
icon: "",
text: "Mascotas",
link: "/seguros/mascotas/",
},
{
icon: "",
text: "Vehículos",
link: "/seguros/vehiculos/",
},
{
icon: "",
text: "Vida",
link: "/seguros/vida/",
},
];
const activeLinkProps = {
style: { fontWeight: "bold" },
};
const navLinks = [
{ to: "/", label: "Inicio", className: "text-lg" },
{ to: "/seguros/decesos", label: "Decesos" },
{ to: "/seguros/hogar", label: "Hogar" },
{ to: "/seguros/salud", label: "Salud" },
{ to: "/seguros/mascotas", label: "Mascotas" },
{ to: "/seguros/vida", label: "Vida" },
{
to: "/seguros/vehiculos",
label: "Vehículos",
className: "data-[active='true']:font-medium",
},
];
const BasicNavbar = React.forwardRef<HTMLElement, NavbarProps>(
({ classNames = {}, ...props }, ref) => {
const navigate = useNavigate();
const [isMenuOpen, setIsMenuOpen] = React.useState(false);
const delayedSetIsMenuOpen = (val: boolean) => {
setTimeout(() => {
setIsMenuOpen(val);
}, 200);
};
return (
<Navbar
ref={ref}
{...props}
classNames={{
base: cn("border-default-100 bg-transparent", {
"bg-white/50": isMenuOpen,
}),
wrapper: "w-full justify-center",
item: "hidden md:flex",
...classNames,
}}
height="60px"
isMenuOpen={isMenuOpen}
onMenuOpenChange={setIsMenuOpen}
>
{/* Left Content */}
<NavbarBrand>
<Button
onPress={() =>
navigate({
to: "/",
viewTransition: true,
})
}
className="mx-0 px-0"
variant="light"
>
<span className="text-lg font-medium text-purple-900">
Victoria<span className="text-gray-500">Seguros</span>
</span>
</Button>
</NavbarBrand>
{/* Center Content */}
<NavbarContent>
{navLinks.map(({ to, label, className }) => (
<Link key={to} to={to} viewTransition activeProps={activeLinkProps}>
{() => (
<NavbarItem className={` text-lg ${className}`}>
{label}
</NavbarItem>
)}
</Link>
))}
<NavbarItem>
<Chip
color="success"
variant="flat"
className=""
startContent={
<span className="iconify size-5 text-default-500 solar--phone-calling-rounded-bold-duotone" />
}
classNames={{
content: "mx-1 items-center font-semibold",
}}
>
<a href="tel:+34633620767">633620767</a>
</Chip>
</NavbarItem>
</NavbarContent>
<NavbarContent />
<NavbarMenuToggle className="text-default-400 md:hidden" />
<NavbarMenu
// className="top-[calc(var(--navbar-height)_-_1px)] max-h-fit bg-default-200/50 shadow-medium backdrop-saturate-150 dark:bg-default-100/50"
motionProps={{
initial: { opacity: 0, y: -20 },
animate: { opacity: 1, y: 0 },
exit: { opacity: 0, y: -20 },
transition: {
ease: "easeInOut",
duration: 0.2,
},
}}
>
<NavbarMenuItem className="">
<Button
className="mt-6"
color="secondary"
fullWidth
onPress={() => {
navigate({
to: "/formulario",
viewTransition: true,
});
}}
>
Calcular precio
</Button>
</NavbarMenuItem>
<NavbarMenuItem>
<div className="justify-center flex">
<Chip
color="success"
variant="flat"
startContent={
<span className="iconify size-5 text-default-500 solar--phone-calling-rounded-bold-duotone" />
}
classNames={{
content: "mx-1 items-center font-semibold",
}}
>
<a href="tel:+34633620767" className="z-50">
633620767
</a>
</Chip>
</div>
</NavbarMenuItem>
{menuItems.map((mi) => (
<NavbarMenuItem
key={mi.text}
className="my-5 text-start font-black font-20 text-default-700 uppercase rounded-lg px-2"
>
<div className="flex gap-2 items-center ">
<Link
to={mi.link}
viewTransition
className="w-full h-full"
onClick={() => delayedSetIsMenuOpen(false)}
activeProps={{
style: {
color: "#000",
},
}}
>
{mi.text}
</Link>
<span className="iconify size-6 solar--alt-arrow-right-line-duotone text-secondary/80" />
</div>
</NavbarMenuItem>
))}
</NavbarMenu>
</Navbar>
);
}
);
BasicNavbar.displayName = "BasicNavbar";
export default BasicNavbar;

View File

@ -0,0 +1,34 @@
import { Card, CardBody } from "@heroui/card";
import { Link } from "@tanstack/react-router";
export function NotFound({ children }: { children?: any }) {
return (
<div className="flex justify-center w-full">
<Card className="w-full max-w-5xl min-h-[200px]">
<CardBody className="p-2 flex justify-center flex-col items-center gap-5">
<div className="text-gray-600 dark:text-gray-400 my-2 flex gap-2 items-center font-semibold">
<span className="iconify size-6 solar--magnifer-zoom-out-bold-duotone " />
{children || <p>La página que buscas no existe.</p>}
</div>
<div>
<p className="flex items-center gap-2 flex-wrap">
<button
onClick={() => window.history.back()}
className="bg-emerald-500 text-white px-2 py-1 rounded uppercase font-black text-sm"
>
Volver
</button>
<Link
to="/"
className="bg-cyan-600 text-white px-2 py-1 rounded uppercase font-black text-sm"
>
Ir a inicio
</Link>
</p>
</div>
</CardBody>
</Card>
</div>
);
}

View File

@ -0,0 +1,5 @@
import { ErrorComponent, ErrorComponentProps } from '@tanstack/react-router'
export function PostErrorComponent({ error }: ErrorComponentProps) {
return <ErrorComponent error={error} />
}

View File

@ -0,0 +1,31 @@
import { useEffect, useState } from "react";
import {useTheme} from "@heroui/use-theme";
import { Button } from "@heroui/button";
import { cn } from "@heroui/theme";
export function ThemeSwitcher() {
const { theme, setTheme } = useTheme();
const [isSelected, setIsSelected] = useState<boolean>(theme === "dark");
useEffect(() => {
const newTheme = isSelected ? "dark" : "light";
setTheme(newTheme);
}, [isSelected, setTheme]);
return (
<Button
size="sm"
onPress={() => setIsSelected(!isSelected)}
isIconOnly
variant="flat"
radius="full"
startContent={
<span
className={cn("size-5 iconify solar--moon-stars-line-duotone", {
"solar--sun-fog-line-duotone": isSelected,
})}
></span>
}
/>
);
}

View File

@ -0,0 +1,5 @@
import { ErrorComponent, ErrorComponentProps } from '@tanstack/react-router'
export function UserErrorComponent({ error }: ErrorComponentProps) {
return <ErrorComponent error={error} />
}

44
src/content/coche.ts Normal file
View File

@ -0,0 +1,44 @@
export const dataChipCoche = [
{
label: "iconify solar--shield-check-line-duotone size-5",
icon: "Asistencia en viaje las 24h desde el Km 0",
},
{
label: "iconify solar--shield-check-line-duotone size-5",
icon: "Vehículo de sustitución por accidente y robo",
},
{
label: "iconify solar--shield-check-line-duotone size-5",
icon: "Daños y robo de equipajes",
},
{
label: "iconify solar--shield-check-line-duotone size-5",
icon: "Repintado del vehículo",
},
{
label: "iconify solar--shield-check-line-duotone size-5",
icon: "Error en carga de combustible",
},
{
label: "iconify solar--shield-check-line-duotone size-5",
icon: "Daños y robo de equipajes",
},
];
export const dataChipMoto = [
{
label: "iconify solar--shield-check-line-duotone size-5",
icon: "Tarificación y contratación 100% online",
},
{
label: "iconify solar--shield-check-line-duotone size-5",
icon: "Con las garantías opcionales más innovadoras",
},
{
label: "iconify solar--shield-check-line-duotone size-5",
icon: "Total personalización de tu seguro",
},
{
label: "iconify solar--shield-check-line-duotone size-5",
icon: "Asistencia en viaje las 24h y desde el kilómetro 0",
},
];

178
src/content/decesos.ts Normal file
View File

@ -0,0 +1,178 @@
export const dataChipDecesos = [
{
label: "iconify solar--shield-check-line-duotone size-5",
icon: "Contratación 100% online",
},
{
label: "iconify solar--shield-check-line-duotone size-5",
icon: "Limpieza dental anual gratuita",
},
{
label: "iconify solar--shield-check-line-duotone size-5",
icon: "Contratable hasta los 80 años",
},
];
export const dataAccordion = [
{
icon: "iconify solar--leaf-line-duotone size-5",
title: "Servicio de sepelio y asistencia por fallecimiento",
key: "sepelio",
descripcion:
"Prestación del servicio de sepelio: arca, carroza fúnebre, servicios religiosos, capilla ardiente, personal, tasas municipales, certificados, gastos médicos, legales y demás trámites de sepelio, coronas, esquelas, sepultura o nicho y lápidas. Se podrá elegir incineración y otros servicios complementarios tales como coche de acompañantes, tanatorio o sala velatorio, siempre que estén disponibles en el lugar, sin que el coste total del servicio pueda superar el límite máximo de la suma asegurada. También se incluyen servicios de asistencia por fallecimiento, como orientación psicológica al duelo o borrado de la identidad digital del fallecido.",
},
{
icon: "iconify solar--earth-line-duotone size-5",
title: "Asistencia en viaje mundial",
key: "viaje",
descripcion:
"Asistencia en caso de accidente, enfermedad y otros imprevistos que puedan ocurrir en tus viajes. Cobertura nacional e internacional. Ampliable hasta 6 o 12 meses en caso de estancias más largas.",
},
{
icon: "iconify solar--diploma-line-duotone size-5",
title: "Asistencia legal y gestoría",
key: "legal",
descripcion:
"Trámites de gestoría en vida y por fallecimiento, asistencia legal telefónica gratuita, asesoramiento para la elaboración de un testamento online, servicio de redacción y revisión de documentos. Primera consulta en despacho de abogados gratuita y hasta 25% de descuento sobre el resto de actuaciones jurídicas.",
},
{
icon: "iconify solar--stethoscope-bold-duotone size-5",
title: "Prestaciones sanitarias y de cuidado personal",
key: "sanidad",
descripcion:
"Cuadro médico y dental a precios preferentes, limpieza bucal anual gratuita, orientación dietética y nutricional telefónica, videoconsulta médica, chat médico, historial médico en la nube y segunda opinión médica internacional, entre otras prestaciones.",
},
{
icon: "iconify solar--gps-line-duotone size-5",
title: "Repatriación de españoles y extranjeros",
key: "repatriacion",
descripcion:
"Repatriación en caso de fallecimiento del asegurado de nacionalidad extranjera con residencia habitual en España o del asegurado español con residencia habitual en el extranjero y a petición expresa de sus familiares, hasta el aeropuerto internacional más próximo al lugar de inhumación, donde la funeraria que vaya a realizar el servicio se hará cargo del mismo.",
},
{
icon: "iconify solar--bone-line-duotone size-5",
title: "Asistencia a mascotas",
key: "mascotas",
descripcion:
"Asistencia a mascotas de las especies perro y gato, destinadas a compañía o uso doméstico. Incluye servicio de incineración individual, red de veterinarios y servicios de bienestar a precios concertados, asesoramiento telefónico sobre diferentes aspectos relacionados con animales de compañía, asistencia en caso de pérdida o extravío y asistencia legal para dueños de mascotas. Además, con el seguro de decesos también podrás añadir la cobertura de Responsabilidad Civil para perros peligrosos y no peligrosos, obligatorio con la nueva Ley de Bienestar Animal.",
},
];
export const servicesAccordion = [
{
icon: "iconify solar--adhesive-plaster-line-duotone size-5",
title: "Acceso a especialistas médicos a precios preferentes",
key: "acceso-especialistas",
descripcion:
"Aunque tu familia goce de una buena salud, es necesario acudir regularmente a revisiones médicas. Con tu seguro de decesos, ponemos a tu disposición la más completa red de centros sanitarios y profesionales médicos a precios preferentes. Se incluyen también acuerdos preferentes en medicina preventiva (reconocimiento médico, test farmacogenético, test de intolerancia alimenticia, test genético preventivo, test PCR Covid-19).",
},
{
icon: "iconify solar--heart-pulse-line-duotone size-5",
title: "Limpieza dental anual gratuita",
key: "viaje",
descripcion:
"Los dentistas recomiendan una asidua limpieza bucal para el cuidado de tus dientes. Gracias a tu seguro podrás disfrutar de una limpieza bucal al año gratis por cada asegurado incluido en tu póliza, además de otros servicios gratuitos o a precios reducidos.",
},
{
icon: "iconify solar--diploma-line-duotone size-5",
title: "Orientación dietética y nutricional telefónica",
key: "orientacion-medica",
descripcion:
"Una alimentación sana y equilibrada ayuda a mantener la salud y el bienestar, a la vez que favorece la prevención de enfermedades. Si lo necesitas, un especialista en dietética y nutrición te asesorará sobre dietas, hábitos alimenticios, dieta para patologías específicas, alteraciones nutricionales, etc.",
},
{
icon: "iconify solar--monitor-camera-line-duotone size-5",
title: "Videoconsulta médica",
key: "videoconsulta-medica",
descripcion:
"Podrás concertar una cita, estés donde estés, con un equipo médico a través de videollamada, para hacer tus consultas de medicina general, pediatría, psicología, fisioterapia, alergología, traumatología, oftalmología, otorrinolaringología y nutrición y dietética. Con posibilidad de receta electrónica.",
},
{
icon: "iconify solar--chat-line-line-duotone size-5",
title: "Chat médico",
key: "duda-medica",
descripcion:
"Ante cualquier duda médica, no hay nada como poder contactar rápidamente con un profesional. Ponemos a tu disposición un chat sin cita previa, donde podrás realizar consultas médicas, subir analíticas o informes y recibir orientación sobre diagnóstico, tratamientos o prevención de enfermedades. Con posibilidad de receta electrónica.",
},
{
icon: "iconify solar--letter-opened-line-duotone size-5",
title: "Historial médico en la nube",
key: "historial",
descripcion:
"Ponemos a tu disposición una plataforma que te permitirá acceder cómodamente a tu historial médico, en cualquier momento y desde cualquier lugar. Así, los profesionales sanitarios que lo precisen, tendrán acceso a toda la información necesaria, en caso de urgencia o necesidad inminente, tanto en el territorio nacional como en el extranjero.",
},
{
icon: "iconify solar--globus-line-duotone size-5",
title: "Segunda opinión médica internacional",
key: "segunda-op-medica",
descripcion:
"En caso de enfermedad grave, gestionamos la solicitud de una segunda opinión médica, con especialistas de referencia a nivel mundial. ",
},
{
icon: "iconify solar--file-line-duotone size-5",
title: "Trámites de gestoría en vida",
key: "tramites",
descripcion:
"Presentación telemática de la solicitud y de la documentación requerida por la Seguridad Social para iniciar los trámites de Jubilación Ordinaria y/o Jubilación de Reglamentos o Convenios. Redacción de borrador y asesoramiento sobre el registro de las últimas voluntades vitales anticipadas (atenciones médicas, cuidados paliativos en caso de enfermedades no reversibles). Informe de reputación digital y solicitud de derecho al olvido.",
},
{
icon: "iconify solar--folder-favourite-bookmark-line-duotone size-5",
title: "Testamento online",
key: "testamento",
descripcion:
"¿Has pensado alguna vez en redactar tu testamento pero no sabes cómo hacerlo? Te ofrecemos la ayuda de un abogado especialista en derecho sucesorio, que te ayudará en la redacción de tu testamento, ajustándolo a tu situación personal.",
},
{
icon: "iconify solar--compass-square-line-duotone size-5",
title: "Orientación psicológica al duelo",
key: "orientacion",
descripcion:
"Nunca estamos preparados para despedir a nuestros seres queridos. Te acompañamos en este duro trance con la ayuda de un psicólogo que te prestará su apoyo para afrontar el duelo. En el momento que nos necesites, allí estaremos.",
},
{
icon: "iconify solar--user-minus-broken size-5",
title: "Borrado de identidad digital del fallecido",
key: "borrado-id",
descripcion:
"Ponemos a tu disposición la posibilidad de solicitar la eliminación de la información personal que pueda aparecer sobre la persona fallecida en las redes sociales.",
},
];
export const petsCards = [
{
title: "Servicio de incineración",
description:
"Gastos de incineración individual, con asesoramiento y coordinación las 24h del día.",
icon: "iconify solar--bone-broken",
},
{
title: "Red de veterinarios",
description: "Extensa red de veterinarios a precios concertados.",
icon: "iconify solar--users-group-two-rounded-line-duotone",
},
{
title: "Servicios de bienestar",
description:
"Red de centros y servicios a precios concertados: peluquerías caninas, tiendas especializadas, localizador GPS.",
icon: "iconify solar--jar-of-pills-2-line-duotone",
},
{
title: "Residencias caninas",
description:
"Precios concertados para el cuidado y residencia de la mascota.",
icon: "iconify solar--buildings-line-duotone",
},
{
title: "Atención telefónica especializada",
description:
"Asesoramiento sobre hoteles, transporte, criaderos o eventos para animales de compañía. Asistencia en caso de pérdida o extravío.",
icon: "iconify solar--phone-calling-rounded-line-duotone",
},
{
title: "Asistencia jurídica",
description:
"Asistencia legal sobre cualquier cuestión relativa a tu mascota.",
icon: "iconify solar--file-line-duotone",
},
];

105
src/content/hogar.ts Normal file
View File

@ -0,0 +1,105 @@
export const dataChipHome = [
{
label: "iconify solar--shield-check-line-duotone size-5",
icon: "Vivienda habitual y segunda vivienda",
},
{
label: "iconify solar--shield-check-line-duotone size-5",
icon: "Atención telefónica 24 horas",
},
{
label: "iconify solar--shield-check-line-duotone size-5",
icon: "Contrata lo necesario",
},
];
export const indexCards = [
{
title: "Atención a medida",
icon: "iconify solar--headphones-round-sound-line-duotone",
},
{
title: "Líbrate del papeleo",
icon: "iconify solar--file-corrupted-line-duotone",
},
{
title: "Sin complicaciones",
icon: "iconify solar--meditation-round-line-duotone",
},
{
title: "Presupuestos adaptables",
icon: "iconify solar--wallet-money-line-duotone",
},
];
export const homeCards = [
{
title: "Asistencia Bricohogar",
description:
"Disfruta sin coste de ayuda profesional a domicilio para tareas de mantenimiento e instalación en tu hogar: fontanería, carpintería, electricidad y más.",
icon: "iconify solar--settings-linear",
},
{
title: "Asistencia en el hogar",
description:
"Profesionales cualificados acudirán gratuitamente a tu domicilio para reparaciones urgentes: fontanería, electricidad, cerrajería, albañilería, pintura, persianas o tapicería.",
icon: "iconify solar--home-line-duotone",
},
{
title: "Asistencia informática",
description:
"Un experto te asesorará en el uso de herramientas informáticas, resolverá incidencias y te ayudará a configurar tu ordenador, tablet o móvil.",
icon: "iconify solar--translation-2-line-duotone",
},
{
title: "Asistencia jurídica",
description:
"Protege a tu familia con asesoría legal completa, tanto judicial como extrajudicial, ante posibles daños personales o materiales.",
icon: "iconify solar--case-line-duotone",
},
];
export const accordionfrequentlyaskedquestions = [
{
title: "¿Es obligatorio el seguro de hogar?",
description:
"El seguro de hogar no es obligatorio, sin embargo, se recomienda tener uno. Una vivienda supone una gran inversión y puede sufrir la rotura de una tubería, un incendio o un robo, entre otros imprevistos. Contar con un buen seguro te dará tranquilidad, ya que algunos de estos percances pueden provocar daños muy graves y suponer un importante desembolso económico.",
},
{
title: "¿Es obligatorio contratar un seguro de Hogar con la hipoteca?",
description:
"A la hora de pedir un préstamo hipotecario, una de las cuestiones que más dudas genera a los propietarios es la de la contratación del seguro de Hogar. La vivienda hipotecada deberá contar con un seguro de protección básico contra incendios, te contamos todo en nuestro blog.",
},
{
title: "¿Qué cubre el seguro de hogar?",
description:
"Con el Seguro de Hogar de Victoria Seguros tendrás a tu disposición las coberturas más completas del mercado para que siempre te sientas protegido, tales como: Goteras y filtraciones, Desatasco, Fontanería urgente sin daños, Filtraciones por defecto en el sellado de juntas en aparatos sanitarios, Exceso de consumo de agua, Robo en Anexo y Restauración estética para bienes mobiliarios... Además, para asegurar tu tranquilidad, te ofrecemos amplios límites en las coberturas de Robo y Daños por agua. Asimismo, con tu seguro de hogar podrás disfrutar de una selección de servicios tales como: Asistencia en el Hogar e Informática, Bricohogar, Reparación de electrodomésticos de línea blanca, Navegación segura en internet, Línea médica, entre otros.",
},
{
title: "¿Qué cubre la responsabilidad civil de un seguro de hogar?",
description:
"La cobertura de responsabilidad civil cubre los daños materiales o personales que tanto tú, como tu familia o la propia vivienda, podáis causar accidentalmente a terceras personas. Por ejemplo, si un escape de agua en tu casa afectase a la vivienda del vecino de abajo, la aseguradora se haría cargo del problema ocasionado, abonando los gastos de reparación.",
},
{
title: "¿Qué es el continente en un seguro de hogar?",
description:
"El continente es toda la estructura de tu vivienda (muros, paredes, techos, suelo), los elementos fijos (puertas, ventanas, persianas, armarios empotrados) y las instalaciones fijas (calefacción, agua, electricidad). También forman parte del continente las construcciones anexas (garaje, piscina, trastero).",
},
{
title: "¿Qué es el contenido en un seguro de hogar?",
description:
"El contenido está compuesto por los bienes que se encuentran dentro de tu vivienda como los muebles, los electrodomésticos, los aparatos eléctricos... En definitiva, todo aquello que si diésemos la vuelta a la casa y la sacudiésemos, caería al suelo.",
},
{
title: "¿Cuánto cuesta un seguro de hogar?",
description:
"El Seguro de Hogar se calcula en función de los datos de la vivienda proporcionada: tipo de vivienda, superficie, uso, ubicación, personas que la habitan, año de construcción, elementos de seguridad... Con los datos que nos facilites sobre tu casa te recomendaremos unos capitales de contenido y continente y, dado que los precios varían según los factores mencionados anteriormente, es difícil proporcionar un coste exacto. Sin embargo, el coste promedio de un seguro de hogar online en Victoria Seguros es de 180€/año* (*en función de los datos de la vivienda proporcionada).",
},
{
title: "¿No encuentras las garantías o coberturas que necesitas?",
description:
"En Victoria Seguros tenemos disponibles más opciones dentro del Seguro de Hogar. Consulta la posibilidad de incluir garantías como Responsabilidad Civil de perros de raza peligrosa o del cazador, coberturas sobre instalaciones solares o piscinas, etc. Contacta con nosotros y estudiaremos tu caso, sin compromiso.",
},
];

32
src/content/home.ts Normal file
View File

@ -0,0 +1,32 @@
export const homeCards = [
{
title: "Atencióna medida",
description:
"Siempre te atenderá la misma persona durante todo el trayecto de tu contrato..",
icon: "iconify solar--headphones-round-line-duotone",
},
{
title: "Cero complicaciones",
description: "Yo me encargo del papeleo, tú solo disfruta de la tranquilidad. ¡Así de fácil!",
icon: "iconify solar--face-scan-circle-line-duotone",
},
{
title: "Servicios de bienestar",
description:
"Yo me encargo del papeleo, tú solo disfruta de la tranquilidad. ¡Así de fácil!",
icon: "iconify solar--stethoscope-line-duotone",
},
{
title: "Confianza total",
description:
"Te ofrezco asesoramiento personalizado. Aquí no hay sorpresas, solo lo mejor para ti.",
icon: "iconify solar--hand-heart-line-duotone",
},
{
title: "Siempre contigo",
description:
"Cuando lo necesites, estaré aquí. Un seguro que te cuida en cada paso del camino..",
icon: "iconify solar--phone-calling-rounded-line-duotone",
},
];

113
src/content/perros.ts Normal file
View File

@ -0,0 +1,113 @@
export const dataChip = [
{
label: "iconify solar--shield-check-line-duotone size-5",
icon: "Perros peligrosos",
},
{
label: "iconify solar--shield-check-line-duotone size-5",
icon: "Varios perros, una póliza",
},
{
label: "iconify solar--shield-check-line-duotone size-5",
icon: "Hasta 400.000€ Defensa Jurídica",
},
{
label: "iconify solar--shield-check-line-duotone size-5",
icon: "Hasta 400.000€ Responsabilidad Civil",
},
{
label: "iconify solar--shield-check-line-duotone size-5",
icon: "Cobertura nacional o europea",
},
];
export const dataChipCaballos = [
{
label: "iconify solar--shield-check-line-duotone size-5",
icon: "Desde 100€ al año",
},
{
label: "iconify solar--shield-check-line-duotone size-5",
icon: "Varios caballos, una póliza",
},
{
label: "iconify solar--shield-check-line-duotone size-5",
icon: "Descuentos por números",
},
{
label: "iconify solar--shield-check-line-duotone size-5",
icon: "Responsabilidad Civil",
},
{
label: "iconify solar--shield-check-line-duotone size-5",
icon: "Recogida y destrucción de cadáveres",
},
];
export const dataAccordion = [
{
icon: "iconify solar--hand-money-line-duotone size-5",
title: "Responsabilidad Civil General (400.000€)",
key: "sepelio",
descripcion:
"Cubre los daños materiales y personales, así como aquellos perjuicios causados a terceros en su condición de propietario o poseedor del perro.",
},
{
icon: "iconify solar--hand-money-line-duotone size-5",
title: "Gastos de Defensa Jurídica (400.000€)",
key: "viaje",
descripcion:
"Gastos de defensa del propietario del perro derivados de incidentes con terceras personas.",
},
];
export const dataAccordionCaballos = [
{
icon: "iconify solar--shield-up-line-duotone size-5",
title: "Garantiza tu protección frente a cualquier incidente",
key: "proteccion",
descripcion:
"Disfruta con tu caballo de los buenos momentos, una competición, un evento o simplemente montando en el campo, y deja los contratiempos en nuestras manos. El Seguro de Caballos de Victoria Seguros te garantiza la responsabilidad civil como propietario, protegiéndote frente a las posibles consecuencias, tanto materiales como personales, que pudiera ocasionar tu caballo a terceros. Asimismo, incluye la garantía de retirada y destrucción de cadáveres, en caso de muerte del animal.",
},
{
icon: "iconify solar--golf-line-duotone size-5",
title: "Ágil y fácil de contratar",
key: "agilidad",
descripcion:
"Nuestro Seguro de Caballos te ofrece personalización y sencillez en su contratación. Por un lado, la personalización, ya que te permite elegir entre sus dos opciones de contratación, en función del capital de responsabilidad civil que desees asegurar (150.000, 300.000 o 600.000 euros) y, por otro lado, la facilidad, al ofrecer precios cerrados con cuatro tarifas que dependen de la edad del animal, así como la posibilidad de incluir todos tus caballos en una sola póliza.Este seguro está dirigido a caballos de entre 1 y 20 años.",
},
{
icon: "iconify solar--wallet-money-line-duotone",
title: "Desde 100 euros al año y con descuentos adicionales",
key: "gastos",
descripcion:
"Tener un caballo es una gran responsabilidad. Sin embargo, desde solo 100 euros al año, podrás tener la tranquilidad de estar protegido ante posibles daños a terceros.Además de tener un precio competitivo, podrás beneficiarte por incluir todos tus caballos en una sola póliza, obteniendo descuentos al asegurar más de cinco equinos en la misma.",
},
];
export const petsCards = [
{
title: "Contrata tu seguro y descarga tu certificado",
description:
"A través de un sencillo proceso de contratación online podrás obtener tu certificado en tan solo unos minutos y tenerlo siempre a mano cuando lo necesites.",
icon: "iconify solar--diploma-line-duotone",
},
{
title: "Tranquilidad ante los imprevistos",
description:
"El Seguro de Responsabilidad Civil de perros cubre los daños que tu mascota ocasione a una tercera persona, así como los gastos de defensa derivados de los mismos.",
icon: "iconify solar--like-line-duotone",
},
{
title: "Máxima protección",
description:
"Este seguro te ofrece cobertura en todo el territorio nacional (con posibilidad de ampliar a la Unión Europea) con un capital de 400.000€ por siniestro/año y sin franquicia.",
icon: "iconify solar--shield-up-line-duotone",
},
{
title: "Tus perros en la misma póliza",
description:
"Precios concertados para el cuidado y residencia de la mascota.",
icon: "iconify solar--buildings-line-duotone",
},
];

74
src/content/salud.ts Normal file
View File

@ -0,0 +1,74 @@
export const dataChipHealth = [
{
label: "iconify solar--shield-check-line-duotone size-5",
icon: "Respaldo de Caser aseguradora ",
},
{
label: "iconify solar--shield-check-line-duotone size-5",
icon: "Tratamientos personalizados",
},
{
label: "iconify solar--shield-check-line-duotone size-5",
icon: "Telemedicina, selfie health...",
},
{
label: "iconify solar--shield-check-line-duotone size-5",
icon: "App Salud, cheque dental...",
},
];
export const dataCardDemo = [
{
title: "Un seguro para todas las edades",
icon: "iconify solar--heart-pulse-line-duotone size-20",
description:
"Incluye pruebas, urgencias, especialistas, hospitalización... Y, además, coberturas para el embarazo, posparto y la infancia.",
},
{
title: "Cuadro médico de calidad",
icon: "iconify solar--stethoscope-line-duotone size-20",
description:
"Con más de 45.000 profesionales y 13.000 centros para citas presenciales y telemedicina (llamadas y videollamadas ilimitadas).",
},
{
title: "Ahorro en tus gastos médicos",
icon: "iconify solar--wallet-money-line-duotone size-20",
description:
"10 primeros servicios médicos sin coste por cada asegurado y reembolso en gastos de farmacia y vacunas infantiles.",
},
];
export const dataCardHealth = [
{
title: "Te devolvemos el 50% de lo que te gastes en tu óptica",
icon: "iconify solar--heart-pulse-line-duotone size-20",
description:
"La vista es un elemento fundamental, nuestro sentido más desarrollado. En Victoria Seguros, queremos que tu mirada tenga una graduación inmejorable, por ello, te ayudamos en la compra o renovación de tus gafas y lentes de contacto.",
},
{
title:
"Te reembolsamos el 50% de tus gastos en farmacia y vacunas infantiles",
icon: "iconify solar--stethoscope-line-duotone size-20",
description:
"En ocasiones, los medicamentos o vacunas para los peques no están cubiertos por la sanidad pública, lo que supone un gasto extra para tu bolsillo. Nosotros, abaratamos estos costes.",
},
{
title:
"Te devolvemos hasta 600€ por la compra de cualquier modalidad de crioconservación de células madre Biocord",
icon: "iconify solar--wallet-money-line-duotone size-20",
description:
"Te reembolsamos hasta 600€ por la compra de cualquier modalidad de crioconservación de células madre Biocord, a través de nuestra plataforma de servicios Caser Más Beneficios.",
},
{
title: "Te regalamos un cheque dental por valor de 100€",
icon: "iconify solar--wallet-money-line-duotone size-20",
description:
"Te regalamos un cheque descuento por asegurado por valor de 100€ en Ortodoncias e Implantes ó 30€ en el resto de tratamientos.",
},
{
title: "Descuento de 50€ en maternidad y reproducción asistida",
icon: "iconify solar--wallet-money-line-duotone size-20",
description:
"Descuento de 50€ en maternidad y reproducción asistida o 20€ en nutrición, genética y prevención para la compra de servicios de salud, bienestar y prevención.",
},
];

107
src/content/vida.ts Normal file
View File

@ -0,0 +1,107 @@
export const dataChipVida = [
{
label: "iconify solar--shield-check-line-duotone size-5",
icon: "Fallecimiento",
},
{
label: "iconify solar--shield-check-line-duotone size-5",
icon: "Capital hasta 150.000€",
},
{
label: "iconify solar--shield-check-line-duotone size-5",
icon: "Victoria Seguros Cuidados",
},
{
label: "iconify solar--shield-check-line-duotone size-5",
icon: "25% de descuento en tu seguro",
},
];
export const servicesAccordionLife = [
{
icon: "iconify solar--question-square-line-duotone size-5",
title: "¿Qué es un seguro de vida?",
key: "acceso-especialistas",
descripcion:
"El seguro de Vida supone un respaldo económico para los familiares (beneficiarios de la póliza) de los asegurados tras su muerte o su incapacidad, ya que ellos recibirán la suma del capital asegurado cuando el suceso ocurra. Los beneficiarios del seguro, dependiendo de lo que haya estipulado previamente el asegurado con la aseguradora, podrán percibir esta prestación económica a plazos o como un único pago.",
},
{
icon: "iconify solar--question-square-line-duotone size-5",
title: "¿Para qué sirve un seguro de vida?",
key: "viaje",
descripcion:
"Si contratas un seguro de vida, en caso de fallecer o quedar inválido por cualquier causa, ayudarás a tu familia a salir adelante. Contarán con los recursos necesarios para poder seguir pagando la hipoteca, hacer frente a los gastos familiares... y todo será más fácil para ellos. Todos tenemos la responsabilidad de proteger a aquellos que dependen de nosotros.",
},
{
icon: "iconify solar--question-square-line-duotone size-5",
title: "¿Es obligatorio seguro de vida con la hipoteca?",
key: "orientacion-medica",
descripcion:
"No es algo vinculante pero es cierto que el seguro de Vida es muy útil si se tiene una hipoteca o algún préstamo pendiente, ya que, si al asegurado le sucede algo, su familia estará protegida en este sentido, no teniendo que hacer frente a sus deudas. Pero más allá de eso, no hay que olvidar que, si el asegurado fallece o sufre una incapacidad o enfermedad grave y es el principal sustento económico de la familia, sus familiares pueden quedar desprotegidos al no disponer de sus ingresos económicos, ya que éste no podría trabajar. De manera que este seguro siempre es útil, aunque no se tengan deudas pendientes.",
},
{
icon: "iconify solar--question-square-line-duotone size-5",
title: "¿Cómo cobrar un seguro de Vida tras la muerte de un familiar?",
key: "videoconsulta-medica",
descripcion:
"En la actualidad los seguros de Vida constituyen una garantía de protección no sólo para la persona que contrata la póliza, sino para toda su familia. ¿Cómo cobrar un seguro de Vida tras la muerte de un familiar? Te explicamos como en el siguiente post.",
},
{
icon: "iconify solar--question-square-line-duotone size-5",
title: "¿Cuáles son las diferentes formas de cobrar un seguro de Vida?",
key: "duda-medica",
descripcion:
"A la hora de contratar un seguro de Vida, el tomador establece en qué forma quiere que el beneficiario cobre la indemnización que le corresponde en caso de que él fallezca. A continuación te explicamos las diferentes formas de cobro de un seguro de Vida.",
},
];
export const lifesafeCards = [
{
title: "Segunda opinión médica",
description:
"Si quieres confirmar tu diagnóstico, conocer los mejores tratamientos o consultar tus dudas, un médico especialista nacional o internacional revisará tu caso.",
icon: "iconify solar--health-line-duotone",
},
{
title: "Medicina preventiva",
description: "Si tienes dudas, tienes la posibilidad de realizarte un reconocimiento médico completo para prevenir y detectar a tiempo cualquier problema de salud.",
icon: "iconify solar--stethoscope-line-duotone",
},
{
title: "Teleconsulta",
description:
"Recibe orientación online con un equipo de profesionales médicos. Una forma rápida, directa y sin esperas de solucionar tus dudas de salud, estés donde estés.",
icon: "iconify solar--monitor-camera-line-duotone",
},
{
title: "Orientación médica telefónica",
description:
"Resuelve tus consultas acerca de tu salud o la de tu familia con este servicio telefónico 24h, 365 días al año, atendido por médicos de diferentes especialidades.",
icon: "iconify solar--monitor-smartphone-line-duotone",
},
{
title: "Servicios Legales",
description:
"Incluye los servicios de asesoramiento para elaborar tu Testamento online de forma sencilla y Borrado de huella digital para la eliminación de información personal en redes sociales e Internet.",
icon: "iconify solar--folder-with-files-line-duotone",
},
{
title: "Test genético",
description:
"Conoce, mediante una simple muestra de saliva, tu propensión a padecer algunas de las enfermedades relacionadas con tu constitución genética.",
icon: "iconify solar--dna-bold-duotone",
},
{
title: "Test nutricional",
description:
"Descubre la dieta que necesitas con un test genético que analiza cómo tu cuerpo asimila los alimentos y cómo influyen en tu salud y peso.",
icon: "iconify solar--donut-line-duotone",
},
{
title: "Apoyo en desplazamientos médicos",
description:
"Te ayudamos con los trámites para recibir tratamiento fuera de España: encontrar al mejor especialista, agendar tus citas o coordinar tu viaje y estancia.",
icon: "iconify solar--bus-line-duotone",
},
];

5
src/global-middleware.ts Normal file
View File

@ -0,0 +1,5 @@
import { registerGlobalMiddleware } from '@tanstack/react-start'
registerGlobalMiddleware({
middleware: [],
})

431
src/routeTree.gen.ts Normal file
View File

@ -0,0 +1,431 @@
/* eslint-disable */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// This file was automatically generated by TanStack Router.
// You should NOT make any changes in this file as it will be overwritten.
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
// Import Routes
import { Route as rootRoute } from './routes/__root'
import { Route as IndexImport } from './routes/index'
import { Route as FormularioIndexImport } from './routes/formulario/index'
import { Route as SegurosVidaImport } from './routes/seguros/vida'
import { Route as SegurosSaludImport } from './routes/seguros/salud'
import { Route as SegurosHogarImport } from './routes/seguros/hogar'
import { Route as SegurosDecesosImport } from './routes/seguros/decesos'
import { Route as FormularioPoliticasFormularioImport } from './routes/formulario/politicas-formulario'
import { Route as FormularioPoliticasImport } from './routes/formulario/politicas'
import { Route as SegurosVehiculosIndexImport } from './routes/seguros/vehiculos/index'
import { Route as SegurosMascotasIndexImport } from './routes/seguros/mascotas/index'
import { Route as SegurosVehiculosMotoImport } from './routes/seguros/vehiculos/moto'
import { Route as SegurosVehiculosCocheImport } from './routes/seguros/vehiculos/coche'
import { Route as SegurosMascotasPerrosImport } from './routes/seguros/mascotas/perros'
import { Route as SegurosMascotasCaballosImport } from './routes/seguros/mascotas/caballos'
// Create/Update Routes
const IndexRoute = IndexImport.update({
id: '/',
path: '/',
getParentRoute: () => rootRoute,
} as any)
const FormularioIndexRoute = FormularioIndexImport.update({
id: '/formulario/',
path: '/formulario/',
getParentRoute: () => rootRoute,
} as any)
const SegurosVidaRoute = SegurosVidaImport.update({
id: '/seguros/vida',
path: '/seguros/vida',
getParentRoute: () => rootRoute,
} as any)
const SegurosSaludRoute = SegurosSaludImport.update({
id: '/seguros/salud',
path: '/seguros/salud',
getParentRoute: () => rootRoute,
} as any)
const SegurosHogarRoute = SegurosHogarImport.update({
id: '/seguros/hogar',
path: '/seguros/hogar',
getParentRoute: () => rootRoute,
} as any)
const SegurosDecesosRoute = SegurosDecesosImport.update({
id: '/seguros/decesos',
path: '/seguros/decesos',
getParentRoute: () => rootRoute,
} as any)
const FormularioPoliticasFormularioRoute =
FormularioPoliticasFormularioImport.update({
id: '/formulario/politicas-formulario',
path: '/formulario/politicas-formulario',
getParentRoute: () => rootRoute,
} as any)
const FormularioPoliticasRoute = FormularioPoliticasImport.update({
id: '/formulario/politicas',
path: '/formulario/politicas',
getParentRoute: () => rootRoute,
} as any)
const SegurosVehiculosIndexRoute = SegurosVehiculosIndexImport.update({
id: '/seguros/vehiculos/',
path: '/seguros/vehiculos/',
getParentRoute: () => rootRoute,
} as any)
const SegurosMascotasIndexRoute = SegurosMascotasIndexImport.update({
id: '/seguros/mascotas/',
path: '/seguros/mascotas/',
getParentRoute: () => rootRoute,
} as any)
const SegurosVehiculosMotoRoute = SegurosVehiculosMotoImport.update({
id: '/seguros/vehiculos/moto',
path: '/seguros/vehiculos/moto',
getParentRoute: () => rootRoute,
} as any)
const SegurosVehiculosCocheRoute = SegurosVehiculosCocheImport.update({
id: '/seguros/vehiculos/coche',
path: '/seguros/vehiculos/coche',
getParentRoute: () => rootRoute,
} as any)
const SegurosMascotasPerrosRoute = SegurosMascotasPerrosImport.update({
id: '/seguros/mascotas/perros',
path: '/seguros/mascotas/perros',
getParentRoute: () => rootRoute,
} as any)
const SegurosMascotasCaballosRoute = SegurosMascotasCaballosImport.update({
id: '/seguros/mascotas/caballos',
path: '/seguros/mascotas/caballos',
getParentRoute: () => rootRoute,
} as any)
// Populate the FileRoutesByPath interface
declare module '@tanstack/react-router' {
interface FileRoutesByPath {
'/': {
id: '/'
path: '/'
fullPath: '/'
preLoaderRoute: typeof IndexImport
parentRoute: typeof rootRoute
}
'/formulario/politicas': {
id: '/formulario/politicas'
path: '/formulario/politicas'
fullPath: '/formulario/politicas'
preLoaderRoute: typeof FormularioPoliticasImport
parentRoute: typeof rootRoute
}
'/formulario/politicas-formulario': {
id: '/formulario/politicas-formulario'
path: '/formulario/politicas-formulario'
fullPath: '/formulario/politicas-formulario'
preLoaderRoute: typeof FormularioPoliticasFormularioImport
parentRoute: typeof rootRoute
}
'/seguros/decesos': {
id: '/seguros/decesos'
path: '/seguros/decesos'
fullPath: '/seguros/decesos'
preLoaderRoute: typeof SegurosDecesosImport
parentRoute: typeof rootRoute
}
'/seguros/hogar': {
id: '/seguros/hogar'
path: '/seguros/hogar'
fullPath: '/seguros/hogar'
preLoaderRoute: typeof SegurosHogarImport
parentRoute: typeof rootRoute
}
'/seguros/salud': {
id: '/seguros/salud'
path: '/seguros/salud'
fullPath: '/seguros/salud'
preLoaderRoute: typeof SegurosSaludImport
parentRoute: typeof rootRoute
}
'/seguros/vida': {
id: '/seguros/vida'
path: '/seguros/vida'
fullPath: '/seguros/vida'
preLoaderRoute: typeof SegurosVidaImport
parentRoute: typeof rootRoute
}
'/formulario/': {
id: '/formulario/'
path: '/formulario'
fullPath: '/formulario'
preLoaderRoute: typeof FormularioIndexImport
parentRoute: typeof rootRoute
}
'/seguros/mascotas/caballos': {
id: '/seguros/mascotas/caballos'
path: '/seguros/mascotas/caballos'
fullPath: '/seguros/mascotas/caballos'
preLoaderRoute: typeof SegurosMascotasCaballosImport
parentRoute: typeof rootRoute
}
'/seguros/mascotas/perros': {
id: '/seguros/mascotas/perros'
path: '/seguros/mascotas/perros'
fullPath: '/seguros/mascotas/perros'
preLoaderRoute: typeof SegurosMascotasPerrosImport
parentRoute: typeof rootRoute
}
'/seguros/vehiculos/coche': {
id: '/seguros/vehiculos/coche'
path: '/seguros/vehiculos/coche'
fullPath: '/seguros/vehiculos/coche'
preLoaderRoute: typeof SegurosVehiculosCocheImport
parentRoute: typeof rootRoute
}
'/seguros/vehiculos/moto': {
id: '/seguros/vehiculos/moto'
path: '/seguros/vehiculos/moto'
fullPath: '/seguros/vehiculos/moto'
preLoaderRoute: typeof SegurosVehiculosMotoImport
parentRoute: typeof rootRoute
}
'/seguros/mascotas/': {
id: '/seguros/mascotas/'
path: '/seguros/mascotas'
fullPath: '/seguros/mascotas'
preLoaderRoute: typeof SegurosMascotasIndexImport
parentRoute: typeof rootRoute
}
'/seguros/vehiculos/': {
id: '/seguros/vehiculos/'
path: '/seguros/vehiculos'
fullPath: '/seguros/vehiculos'
preLoaderRoute: typeof SegurosVehiculosIndexImport
parentRoute: typeof rootRoute
}
}
}
// Create and export the route tree
export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/formulario/politicas': typeof FormularioPoliticasRoute
'/formulario/politicas-formulario': typeof FormularioPoliticasFormularioRoute
'/seguros/decesos': typeof SegurosDecesosRoute
'/seguros/hogar': typeof SegurosHogarRoute
'/seguros/salud': typeof SegurosSaludRoute
'/seguros/vida': typeof SegurosVidaRoute
'/formulario': typeof FormularioIndexRoute
'/seguros/mascotas/caballos': typeof SegurosMascotasCaballosRoute
'/seguros/mascotas/perros': typeof SegurosMascotasPerrosRoute
'/seguros/vehiculos/coche': typeof SegurosVehiculosCocheRoute
'/seguros/vehiculos/moto': typeof SegurosVehiculosMotoRoute
'/seguros/mascotas': typeof SegurosMascotasIndexRoute
'/seguros/vehiculos': typeof SegurosVehiculosIndexRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/formulario/politicas': typeof FormularioPoliticasRoute
'/formulario/politicas-formulario': typeof FormularioPoliticasFormularioRoute
'/seguros/decesos': typeof SegurosDecesosRoute
'/seguros/hogar': typeof SegurosHogarRoute
'/seguros/salud': typeof SegurosSaludRoute
'/seguros/vida': typeof SegurosVidaRoute
'/formulario': typeof FormularioIndexRoute
'/seguros/mascotas/caballos': typeof SegurosMascotasCaballosRoute
'/seguros/mascotas/perros': typeof SegurosMascotasPerrosRoute
'/seguros/vehiculos/coche': typeof SegurosVehiculosCocheRoute
'/seguros/vehiculos/moto': typeof SegurosVehiculosMotoRoute
'/seguros/mascotas': typeof SegurosMascotasIndexRoute
'/seguros/vehiculos': typeof SegurosVehiculosIndexRoute
}
export interface FileRoutesById {
__root__: typeof rootRoute
'/': typeof IndexRoute
'/formulario/politicas': typeof FormularioPoliticasRoute
'/formulario/politicas-formulario': typeof FormularioPoliticasFormularioRoute
'/seguros/decesos': typeof SegurosDecesosRoute
'/seguros/hogar': typeof SegurosHogarRoute
'/seguros/salud': typeof SegurosSaludRoute
'/seguros/vida': typeof SegurosVidaRoute
'/formulario/': typeof FormularioIndexRoute
'/seguros/mascotas/caballos': typeof SegurosMascotasCaballosRoute
'/seguros/mascotas/perros': typeof SegurosMascotasPerrosRoute
'/seguros/vehiculos/coche': typeof SegurosVehiculosCocheRoute
'/seguros/vehiculos/moto': typeof SegurosVehiculosMotoRoute
'/seguros/mascotas/': typeof SegurosMascotasIndexRoute
'/seguros/vehiculos/': typeof SegurosVehiculosIndexRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths:
| '/'
| '/formulario/politicas'
| '/formulario/politicas-formulario'
| '/seguros/decesos'
| '/seguros/hogar'
| '/seguros/salud'
| '/seguros/vida'
| '/formulario'
| '/seguros/mascotas/caballos'
| '/seguros/mascotas/perros'
| '/seguros/vehiculos/coche'
| '/seguros/vehiculos/moto'
| '/seguros/mascotas'
| '/seguros/vehiculos'
fileRoutesByTo: FileRoutesByTo
to:
| '/'
| '/formulario/politicas'
| '/formulario/politicas-formulario'
| '/seguros/decesos'
| '/seguros/hogar'
| '/seguros/salud'
| '/seguros/vida'
| '/formulario'
| '/seguros/mascotas/caballos'
| '/seguros/mascotas/perros'
| '/seguros/vehiculos/coche'
| '/seguros/vehiculos/moto'
| '/seguros/mascotas'
| '/seguros/vehiculos'
id:
| '__root__'
| '/'
| '/formulario/politicas'
| '/formulario/politicas-formulario'
| '/seguros/decesos'
| '/seguros/hogar'
| '/seguros/salud'
| '/seguros/vida'
| '/formulario/'
| '/seguros/mascotas/caballos'
| '/seguros/mascotas/perros'
| '/seguros/vehiculos/coche'
| '/seguros/vehiculos/moto'
| '/seguros/mascotas/'
| '/seguros/vehiculos/'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
FormularioPoliticasRoute: typeof FormularioPoliticasRoute
FormularioPoliticasFormularioRoute: typeof FormularioPoliticasFormularioRoute
SegurosDecesosRoute: typeof SegurosDecesosRoute
SegurosHogarRoute: typeof SegurosHogarRoute
SegurosSaludRoute: typeof SegurosSaludRoute
SegurosVidaRoute: typeof SegurosVidaRoute
FormularioIndexRoute: typeof FormularioIndexRoute
SegurosMascotasCaballosRoute: typeof SegurosMascotasCaballosRoute
SegurosMascotasPerrosRoute: typeof SegurosMascotasPerrosRoute
SegurosVehiculosCocheRoute: typeof SegurosVehiculosCocheRoute
SegurosVehiculosMotoRoute: typeof SegurosVehiculosMotoRoute
SegurosMascotasIndexRoute: typeof SegurosMascotasIndexRoute
SegurosVehiculosIndexRoute: typeof SegurosVehiculosIndexRoute
}
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
FormularioPoliticasRoute: FormularioPoliticasRoute,
FormularioPoliticasFormularioRoute: FormularioPoliticasFormularioRoute,
SegurosDecesosRoute: SegurosDecesosRoute,
SegurosHogarRoute: SegurosHogarRoute,
SegurosSaludRoute: SegurosSaludRoute,
SegurosVidaRoute: SegurosVidaRoute,
FormularioIndexRoute: FormularioIndexRoute,
SegurosMascotasCaballosRoute: SegurosMascotasCaballosRoute,
SegurosMascotasPerrosRoute: SegurosMascotasPerrosRoute,
SegurosVehiculosCocheRoute: SegurosVehiculosCocheRoute,
SegurosVehiculosMotoRoute: SegurosVehiculosMotoRoute,
SegurosMascotasIndexRoute: SegurosMascotasIndexRoute,
SegurosVehiculosIndexRoute: SegurosVehiculosIndexRoute,
}
export const routeTree = rootRoute
._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>()
/* ROUTE_MANIFEST_START
{
"routes": {
"__root__": {
"filePath": "__root.tsx",
"children": [
"/",
"/formulario/politicas",
"/formulario/politicas-formulario",
"/seguros/decesos",
"/seguros/hogar",
"/seguros/salud",
"/seguros/vida",
"/formulario/",
"/seguros/mascotas/caballos",
"/seguros/mascotas/perros",
"/seguros/vehiculos/coche",
"/seguros/vehiculos/moto",
"/seguros/mascotas/",
"/seguros/vehiculos/"
]
},
"/": {
"filePath": "index.tsx"
},
"/formulario/politicas": {
"filePath": "formulario/politicas.tsx"
},
"/formulario/politicas-formulario": {
"filePath": "formulario/politicas-formulario.tsx"
},
"/seguros/decesos": {
"filePath": "seguros/decesos.tsx"
},
"/seguros/hogar": {
"filePath": "seguros/hogar.tsx"
},
"/seguros/salud": {
"filePath": "seguros/salud.tsx"
},
"/seguros/vida": {
"filePath": "seguros/vida.tsx"
},
"/formulario/": {
"filePath": "formulario/index.tsx"
},
"/seguros/mascotas/caballos": {
"filePath": "seguros/mascotas/caballos.tsx"
},
"/seguros/mascotas/perros": {
"filePath": "seguros/mascotas/perros.tsx"
},
"/seguros/vehiculos/coche": {
"filePath": "seguros/vehiculos/coche.tsx"
},
"/seguros/vehiculos/moto": {
"filePath": "seguros/vehiculos/moto.tsx"
},
"/seguros/mascotas/": {
"filePath": "seguros/mascotas/index.tsx"
},
"/seguros/vehiculos/": {
"filePath": "seguros/vehiculos/index.tsx"
}
}
}
ROUTE_MANIFEST_END */

27
src/router.tsx Normal file
View File

@ -0,0 +1,27 @@
import { createRouter as createTanStackRouter } from "@tanstack/react-router";
import { QueryClient } from "@tanstack/react-query";
import { routeTree } from "./routeTree.gen";
import { DefaultCatchBoundary } from "./components/DefaultCatchBoundary";
import { routerWithQueryClient } from "@tanstack/react-router-with-query";
import { NotFound } from "./components/NotFound";
export function createRouter() {
const queryClient = new QueryClient();
return routerWithQueryClient(
createTanStackRouter({
routeTree,
context: { queryClient },
defaultPreload: "intent",
scrollRestoration: true,
defaultErrorComponent: DefaultCatchBoundary,
defaultNotFoundComponent: () => <NotFound />,
}),
queryClient
);
}
declare module "@tanstack/react-router" {
interface Register {
router: ReturnType<typeof createRouter>;
}
}

110
src/routes/__root.tsx Normal file
View File

@ -0,0 +1,110 @@
import { QueryClient } from "@tanstack/react-query";
import {
HeadContent,
Outlet,
Scripts,
createRootRouteWithContext,
} from "@tanstack/react-router";
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
import * as React from "react";
import { Toaster } from "sonner";
import { DefaultCatchBoundary } from "~/components/DefaultCatchBoundary";
import { Footer } from "~/components/Footer";
import BasicNavbar from "~/components/NavBarComponents";
import { NotFound } from "~/components/NotFound";
import appCss from "~/styles/app.css?url";
import { seo } from "~/utils/seo";
import { CookiesProvider, useCookies } from "react-cookie";
import Clarity from "@microsoft/clarity";
// import { createServerFn } from "@tanstack/react-start";
// import { getCookie } from "@tanstack/react-start/server";
import CookieConsent from "~/components/Cookies";
// const cookieValue = createServerFn().s(() => getCookie("cookie-vs"));
export const Route = createRootRouteWithContext<{
queryClient: QueryClient;
}>()({
head: () => ({
meta: [
{
charSet: "utf-8",
},
{
name: "viewport",
content: "width=device-width, initial-scale=1",
},
...seo({
title: "Victoria Seguros | Tú aseguradora de confianza",
description: `La aseguradora n1 de Alicante y Costa del Sol `,
keywords:
"seguros, seguros en Alicante, seguros en Murcia, seguros en la Costa, seguros de decesos, seguros de vida, seguros de salud, seguro de fallecimiento, cobertura funeraria, asistencia en decesos, póliza de vida, póliza de salud, seguro médico, seguro familiar, protección financiera, seguro para extranjeros, seguro de accidentes, servicio funerario, cremación, inhumación, ataúd, seguros completos, seguros económicos, aseguradora en Alicante, aseguradora en Murcia",
}),
],
links: [
{ rel: "stylesheet", href: appCss },
{ rel: "manifest", href: "/site.webmanifest", color: "#fffff" },
{ rel: "icon", href: "/favicon.ico" },
],
}),
// beforeLoad: async () => {
// const cookieV = await cookieValue();
// return {
// haveCookies: cookieV,
// };
// },
errorComponent: (props) => {
return (
<RootDocument>
<DefaultCatchBoundary {...props} />
</RootDocument>
);
},
notFoundComponent: () => <NotFound />,
component: RootComponent,
});
function RootComponent() {
return (
<CookiesProvider>
<RootDocument>
<Toaster position="top-center" richColors />
<Outlet />
</RootDocument>
</CookiesProvider>
);
}
function RootDocument({ children }: { children: React.ReactNode }) {
const projectId = "qxxlp3g93r";
Clarity.init(projectId);
// const { haveCookies } = Route.useRouteContext();
// console.log(haveCookies);
// if (haveCookies === "false") {
// Clarity.consent(false);
// }
return (
<html>
<head>
<HeadContent />
</head>
<body>
<div className="flex flex-col min-h-screen">
<div className="flex-1">
<BasicNavbar />
{children}
<div className="sticky bottom-0 z-50">
<CookieConsent />
</div>
</div>
<div className="w-full">
<Footer />
</div>
</div>
<TanStackRouterDevtools position="bottom-right" />
<Scripts />
</body>
</html>
);
}

View File

@ -0,0 +1,242 @@
import { createFileRoute, Link } from "@tanstack/react-router";
import { Form } from "@heroui/form";
import { Input, Textarea } from "@heroui/input";
import { Select, SelectItem } from "@heroui/select";
import { Card, CardBody, CardFooter, CardHeader } from "@heroui/card";
import { Button } from "@heroui/button";
import { useMutation } from "@tanstack/react-query";
import { toast } from "sonner";
import axios from "axios";
import { z } from "zod";
import { zodValidator } from "@tanstack/zod-adapter";
import { Checkbox } from "@heroui/checkbox";
const searchSchema = z.object({
seguro: z.string().catch(""),
});
export const Route = createFileRoute("/formulario/")({
validateSearch: zodValidator(searchSchema),
component: RouteComponent,
});
interface PropsForm {
// TODO: Replace zod infer
name?: string;
time?: string;
description?: string;
email?: string;
telefono?: string;
}
const seguroItems = {
vehiculo: [
"Número de matricula",
"Compañía anterior",
"5 últimos dígitos póliza anterior",
"Fecha de nacimiento",
"Fecha de carnet",
"Código postal",
],
vida: ["Altura", "Fecha de nacimiento", "Sexo", "Peso"],
decesos: [
"Fecha de nacimiento de los asegurados",
"Número de asegurados",
"Código postal",
],
mascotas: [
"Fecha de nacimiento de la mascota",
"Código postal",
"Número de chip",
],
};
function RouteComponent() {
const navigate = Route.useNavigate();
const { seguro } = Route.useSearch();
const { mutate, isPending } = useMutation({
mutationKey: ["send-email"],
mutationFn: async (data: PropsForm) => {
const { name, time, description, email, telefono } = data;
await axios.post(
"https://sender.p3n4lv3r.es/sender",
{
to: "p3n4lv3r@gmail.com",
subject: "Correo decesos",
message: `
<p>Nombre: ${name} </p>
<p>Horario: ${time} </p>
<br />
<p>Descripción: ${description} </p>
<p>Teléfono: ${telefono}</p>
<p>Email: ${email} </p>
`,
},
{
headers: {
"Content-Type": "application/json",
},
}
);
},
onSuccess: () => {
toast.success("Gracias por contactar 📬", { id: "email-toast" });
navigate({
to: "/",
viewTransition: true,
replace: true,
})
},
onError: () => {
toast.error("Error al enviar petición", { id: "email-toast" });
},
});
const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const formData = new FormData(event.currentTarget);
const name = formData.get("nombre") as string;
const time = formData.get("horario") as string;
const description = formData.get("descripcion") as string;
const email = formData.get("email") as string;
const telefono = formData.get("telefono") as string;
toast.loading("Enviando", { id: "email-toast" });
mutate({ name, time, description, email, telefono } satisfies PropsForm);
};
return (
<>
<div className="flex flex-col justify-center items-center gap-4 mx-4 mt-2">
<p className="text-xl text-secondary">
Formulario de{" "}
<span className="p-2 text-primary-500 bg-primary-100 rounded-lg ">
{seguro || "contacto"}
</span>
</p>
<p className="text-sm text-default-500 mb-2">
Cuentanos lo que necesitas. Te ayudamos.
</p>
</div>
<div className="flex flex-col justify-center items-center my-2 px-4 w-full">
<Card className="w-full max-w-2xl">
<CardHeader className="justify-end pb-0">
<p className="text-tiny">
<span className="text-danger">*</span> campos obligatorios
</p>
</CardHeader>
<Form validationBehavior="native" className="" onSubmit={onSubmit}>
<CardBody className="grid md:grid-cols-1 gap-3 ">
<Input name="nombre" label="Nombre" isRequired />
<div className="grid sm:grid-cols-2 gap-2">
<Input name="email" label="Email" />
<Input name="telefono" label="Teléfono" isRequired />
</div>
<Textarea name="descripcion" label="Descripción" isRequired />
<div className="justify-end flex p-2">
<Checkbox name="acepto" isRequired size="sm" defaultSelected>
Acepto la política de{" "}
</Checkbox>
<Link
to="/formulario/politicas-formulario"
className="text-primary text-sm ml-1"
>
privacidad
</Link>
</div>
<p className="text-tiny text-default-500">
¿ Prefiere tener un horario de contacto ?
</p>
<Select
name="horario"
label="Horario"
defaultSelectedKeys={["mor"]}
>
<SelectItem key="9:30 - 14:00">Mañana 9:30 - 14:00</SelectItem>
<SelectItem key="16:00 - 20:00">Tarde 16:00 - 20:00</SelectItem>
</Select>
</CardBody>
<CardFooter className="justify-between gap-4">
<Button
type="submit"
color="danger"
onPress={() =>
navigate({
to: "/",
viewTransition: true,
replace: true,
})
}
>
Atrás
</Button>
<Button type="submit" isLoading={isPending}>
Enviar
</Button>
</CardFooter>
</Form>
</Card>
<div className="p-2 mt-2 rounded-lg bg-white">
{seguro && (
<p className="font-semibold mb-2">Recomendamos tener a mano:</p>
)}
<div className="text-sm">
{seguro === "vehiculo" && (
<div>
<ul className="grid gap-2">
{seguroItems?.vehiculo.map((e) => (
<li className="flex items-center justify-start">
{" "}
<span className="iconify size-4 solar--alt-arrow-right-outline" />
{e}
</li>
))}
</ul>
</div>
)}
{seguro === "vida" && (
<div>
<ul className="grid gap-2">
{seguroItems?.vida.map((e) => (
<li className="flex items-center justify-start">
{" "}
<span className="iconify size-4 solar--alt-arrow-right-outline" />
{e}
</li>
))}
</ul>
</div>
)}
{(seguro === "decesos" || seguro === "salud") && (
<div>
<ul className="grid gap-2">
{seguroItems?.decesos.map((e) => (
<li className="flex items-center justify-start">
{" "}
<span className="iconify size-4 solar--alt-arrow-right-outline" />
{e}
</li>
))}
</ul>
</div>
)}
{seguro === "mascotas" && (
<div className="p-2 bg-default-500">
<ul className="grid gap-2">
{seguroItems?.mascotas.map((e) => (
<li className="flex items-center justify-start">
{" "}
<span className="iconify size-4 solar--alt-arrow-right-outline" />
{e}
</li>
))}
</ul>
</div>
)}
</div>
</div>
</div>
</>
);
}

View File

@ -0,0 +1,91 @@
import { createFileRoute } from "@tanstack/react-router";
export const Route = createFileRoute("/formulario/politicas-formulario")({
component: RouteComponent,
});
function RouteComponent() {
return (
<div className="text-gray-800 bg-gray-50 px-6 py-10">
<main className="max-w-4xl mx-auto space-y-10">
<h1 className="text-4xl font-bold text-blue-900">Política de Privacidad del Formulario</h1>
<section className="space-y-4 text-lg leading-relaxed">
<p>
En el sitio web <strong>www.victoriaseguros.es</strong> nos comprometemos a proteger tu privacidad. Esta política explica cómo
recogemos, tratamos y protegemos tus datos personales, en cumplimiento del Reglamento (UE) 2016/679 (RGPD), la Ley Orgánica 3/2018 (LOPDGDD)
y la Ley 34/2002 (LSSI-CE).
</p>
</section>
<section className="space-y-4">
<h2 className="text-2xl font-semibold">Datos recopilados a través del formulario</h2>
<p>
Cuando completas nuestro formulario, recopilamos los siguientes datos personales:
</p>
<ul className="list-disc list-inside pl-2 space-y-1">
<li>Nombre</li>
<li>Correo electrónico</li>
<li>Teléfono</li>
</ul>
<p>
La finalidad del tratamiento es poder gestionar y responder a tus solicitudes de información, así como realizar un seguimiento de tu interés.
La base legal es tu consentimiento explícito, otorgado al enviar el formulario.
</p>
</section>
<section className="space-y-4">
<h2 className="text-2xl font-semibold">Uso de Mailjet como encargado del tratamiento</h2>
<p>
Utilizamos la plataforma <strong>Mailjet</strong> para gestionar el envío de comunicaciones relacionadas con el formulario. Mailjet actúa como
encargado del tratamiento de los datos y cumple con los estándares europeos de protección de datos.
Puedes consultar su política de privacidad aquí:&nbsp;
<a
href="https://www.mailjet.com/security-privacy/"
className="text-blue-600 underline"
target="_blank"
rel="noopener noreferrer"
>
https://www.mailjet.com/security-privacy/
</a>
</p>
</section>
<section className="space-y-4">
<h2 className="text-2xl font-semibold">Plazo de conservación</h2>
<p>
Conservaremos tus datos únicamente durante el tiempo necesario para atender tu solicitud o mientras mantengamos comunicaciones derivadas del interés mostrado. Posteriormente, los datos serán eliminados de forma segura.
</p>
</section>
<section className="space-y-4">
<h2 className="text-2xl font-semibold">Derechos de protección de datos</h2>
<p>
Puedes ejercer tus derechos de acceso, rectificación, supresión, limitación del tratamiento, portabilidad y oposición, enviando una solicitud
a <strong>info@victoriaseguros.es</strong>, adjuntando una copia de tu documento de identidad.
</p>
</section>
<section className="space-y-4">
<h2 className="text-2xl font-semibold">Medidas de seguridad</h2>
<p>
Aplicamos medidas técnicas y organizativas apropiadas para garantizar la seguridad y confidencialidad de tus datos, evitando su pérdida, mal uso o acceso no autorizado.
</p>
</section>
<section className="pt-6 border-t space-y-2">
<h2 className="text-xl font-semibold mb-4">Contacto del responsable del tratamiento</h2>
<div className="space-y-1 text-gray-700 text-base">
<p><strong>Dirección:</strong> Calle Mayor 8, bajo · 03190 Pilar de la Horadada, Alicante</p>
<p><strong>Teléfono:</strong> +34 633 620 767</p>
<p><strong>Email:</strong> info@victoriaseguros.es</p>
</div>
<div className="mt-4 space-y-1 text-sm text-gray-600">
<p><strong>Lun - Vie:</strong> 09:00 - 21:00</p>
<p><strong>Sáb - Dom:</strong> Cerrado</p>
</div>
</section>
</main>
</div>
);
}

View File

@ -0,0 +1,110 @@
import { createFileRoute } from "@tanstack/react-router";
export const Route = createFileRoute("/formulario/politicas")({
component: RouteComponent,
});
function RouteComponent() {
return (
<div className="text-gray-800 bg-gray-50 px-6 py-10">
<main className="max-w-4xl mx-auto space-y-10">
<h1 className="text-4xl font-bold text-blue-900">Política de Cookies</h1>
<section className="space-y-4 text-lg leading-relaxed">
<p>
En el sitio web <strong>www.victoriaseguros.es</strong>, utilizamos cookies para mejorar la experiencia de navegación,
analizar el uso del sitio y mostrar contenido personalizado.
</p>
<p>
Esta política cumple con la Ley 34/2002 (LSSI-CE), el Reglamento (UE) 2016/679 (RGPD) y la Ley Orgánica 3/2018 (LOPDGDD).
</p>
</section>
<section className="space-y-4">
<h2 className="text-2xl font-semibold">¿Qué son las cookies?</h2>
<p>
Son pequeños archivos de texto que se almacenan en tu navegador cuando visitas una página web. Sirven para recordar
información relevante como tus preferencias, idioma o secciones más visitadas.
</p>
</section>
<section className="space-y-4">
<h2 className="text-2xl font-semibold">Tipos de cookies que utilizamos</h2>
<div>
<h3 className="font-medium">Según la titularidad</h3>
<ul className="list-disc list-inside pl-2 space-y-1">
<li><strong>Propias:</strong> gestionadas directamente por nosotros.</li>
<li><strong>De terceros:</strong> gestionadas por servicios externos como Microsoft Clarity.</li>
</ul>
</div>
<div>
<h3 className="font-medium">Según su finalidad</h3>
<ul className="list-disc list-inside pl-2 space-y-1">
<li><strong>Técnicas:</strong> necesarias para el funcionamiento básico del sitio.</li>
<li><strong>Preferencias:</strong> recuerdan tus configuraciones personales.</li>
<li><strong>Analíticas:</strong> recopilan información sobre el uso del sitio para mejorarlo.</li>
<li><strong>Publicitarias:</strong> permiten mostrar anuncios adaptados a tus intereses.</li>
</ul>
</div>
<div>
<h3 className="font-medium">Según el tiempo de conservación</h3>
<ul className="list-disc list-inside pl-2 space-y-1">
<li><strong>Sesión:</strong> se eliminan al cerrar el navegador.</li>
<li><strong>Persistentes:</strong> permanecen por un periodo definido.</li>
</ul>
</div>
</section>
<section className="space-y-4">
<h2 className="text-2xl font-semibold">Cookies de terceros: Microsoft Clarity</h2>
<p>
Utilizamos <strong>Microsoft Clarity</strong> para entender cómo interactúan los usuarios con nuestro sitio mediante mapas de calor y grabaciones anónimas.
Estas cookies analíticas se instalan <strong>solo con tu consentimiento explícito</strong>.
</p>
<p>
Más información sobre la política de privacidad de Clarity: {' '}
<a
href="https://privacy.microsoft.com/es-es/privacystatement"
className="text-blue-600 underline"
target="_blank"
rel="noopener noreferrer"
>
https://privacy.microsoft.com/es-es/privacystatement
</a>
</p>
</section>
<section className="space-y-4">
<h2 className="text-2xl font-semibold">Consentimiento y gestión</h2>
<p>
Al visitar nuestro sitio por primera vez, verás un banner donde puedes aceptar, rechazar o configurar el uso de cookies.
Puedes modificar tus preferencias en cualquier momento desde el enlace "Configuración de cookies" en el pie de página.
</p>
<p>
Además, puedes gestionar o eliminar cookies desde tu navegador. Consulta la documentación de tu navegador para más información.
</p>
</section>
<section className="space-y-4">
<h2 className="text-2xl font-semibold">¿Qué sucede si rechazas las cookies?</h2>
<p>
Algunas funcionalidades del sitio podrían verse afectadas, como el acceso a áreas privadas, formularios o contenidos personalizados.
</p>
</section>
<section className="pt-6 border-t space-y-2">
<h2 className="text-xl font-semibold mb-4">Contacto</h2>
<div className="space-y-1 text-gray-700 text-base">
<p><strong>Dirección:</strong> Calle Mayor 8, bajo · 03190 Pilar de la Horadada, Alicante</p>
<p><strong>Teléfono:</strong> +34 633 620 767</p>
<p><strong>Email:</strong> info@victoriaseguros.es</p>
</div>
<div className="mt-4 space-y-1 text-sm text-gray-600">
<p><strong>Lun - Vie:</strong> 09:00 - 21:00</p>
<p><strong>Sáb - Dom:</strong> Cerrado</p>
</div>
</section>
</main>
</div>
);
}

349
src/routes/index.tsx Normal file
View File

@ -0,0 +1,349 @@
import { createFileRoute, Link, Outlet } from "@tanstack/react-router";
import { Card, CardBody, CardFooter, CardHeader } from "@heroui/card";
import { Button } from "@heroui/button";
import { Image } from "@heroui/image";
import { indexCards } from "~/content/hogar";
import { seo } from "~/utils/seo";
import appCss from "~/styles/app.css?url";
import RotatingText from "../TextAnimations/RotatingText/RotatingText";
import CountUp from "~/TextAnimations/CountUp/CountUp";
import { motion } from "framer-motion";
import { Chip } from "@heroui/chip";
export const Route = createFileRoute("/")({
head: () => ({
meta: [
{
charSet: "utf-8",
},
{
name: "viewport",
content: "width=device-width, initial-scale=1",
},
...seo({
title: "Victoria Seguros | Tú aseguradora de confianza",
description: `La aseguradora n1 de Alicante y Costa del Sol`,
keywords:
"seguros, alicante, murcia, costa, decesos, vida, salud, muerte, ataud",
}),
],
links: [
{ rel: "stylesheet", href: appCss },
{ rel: "manifest", href: "/site.webmanifest", color: "#fffff" },
{ rel: "icon", href: "/favicon.ico" },
],
}),
component: MainComponent,
});
const insuranceCard = [
{
name: "Decesos",
icon: "iconify solar--leaf-line-duotone size-14",
link: "/seguros/decesos",
},
{
name: "Hogar",
icon: "iconify solar--key-line-duotone size-14",
link: "/seguros/hogar",
},
{
name: "Vehículos",
icon: "iconify solar--wheel-angle-line-duotone size-14",
link: "/seguros/vehiculos/",
},
{
name: "Vida",
icon: "iconify solar--hand-heart-line-duotone size-14",
link: "/seguros/vida",
},
{
name: "Mascotas",
icon: "iconify solar--paw-line-duotone size-14",
link: "/seguros/mascotas",
},
{
name: "Salud",
icon: "iconify solar--heart-pulse-2-line-duotone size-14",
link: "/seguros/salud",
},
];
export function MainComponent() {
const waUrl = `https://wa.me/${+34633620767}`;
const navigate = Route.useNavigate();
return (
<main className="px-2 md:px-8 lg:px-14 min-h-dvh ">
<div className="main-lay rounded py-10">
<div className="min-h-80 rounded-xl w-full flex justify-center items-center ">
<Card
className="bg-inherit rounded-lg w-full max-w-7xl"
shadow="none"
>
<CardBody className="overflow-hidden">
<h1 className="text-4xl font-bold text-center py-6 flex flex-wrap items-center gap-2 justify-center">
Victoria{" "}
{/* <span className="text-default-100 bg-primary px-2 py-2 rounded-lg">
seguros
</span> */}
<RotatingText
texts={["Seguros", "Vida", "Salud"]}
mainClassName="sm:px-2 min-w-40 overflow-hidden justify-center rounded-lg py-2 text-default-100 bg-primary-500 font-semibold text-white"
staggerFrom={"last"}
initial={{ y: "100%" }}
animate={{ y: 0 }}
exit={{ y: "-120%" }}
staggerDuration={0.025}
splitLevelClassName="overflow-hidden pb-0.5 sm:pb-1 md:pb-1"
transition={{
type: "spring",
damping: 30,
stiffness: 400,
}}
rotationInterval={2000}
/>
</h1>
<p className="text-lg font-semibold text-center pb-4">
¿ Buscas un seguro cercano y con alguien de confianza ?
</p>
<div className="flex justify-center items-center gap-2">
<Button
className="font-bold"
endContent={
<span className="iconify cib--whatsapp size-5 text-green-700" />
}
as={Link}
to={waUrl}
>
Whatsapp
</Button>
<Button
color="secondary"
onPress={() =>
navigate({
to: "/formulario",
viewTransition: true,
})
}
endContent={
<span className="iconify size-5 solar--verified-check-broken" />
}
>
Calcular precio
</Button>
</div>
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4 mt-5">
{insuranceCard.map((v) => (
<Card className="px-0" key={v.name}>
<CardHeader className="justify-center">
<h1 className="text-xl">{v?.name}</h1>
</CardHeader>
<CardBody>
<div className="flex justify-center">
<span className={v?.icon} />
</div>
</CardBody>
<CardFooter className="justify-center">
<Button
onPress={() =>
navigate({
to: v.link,
viewTransition: true,
})
}
color="secondary"
>
Ver más
</Button>
</CardFooter>
</Card>
))}
</div>
</CardBody>
</Card>
</div>
</div>
<div className="w-full py-8">
<div className="flex justify-center flex-col items-center gap-4">
<Card className="max-w-5xl overflow-y-hidden" shadow="none">
<CardBody>
<div className="grid md:grid-cols-2 gap-4">
<div className="flex items-center justify-center min-h-[380px]">
<Image
width={400}
height={380}
src="/conocenos.webp"
alt="victoria seguros-mayores"
fallbackSrc="/conocenos.webp"
/>
</div>
<div>
<h1 className="text-lg font-semibold mb-3 flex gap-2 items-center">
<p className="text-2xl font-semibold">
¿Por qué nos contratan?
</p>
</h1>
<div className="flex flex-col gap-2">
<p>
En nuestra aseguradora, apostamos por un trato
verdaderamente personalizado. Contamos con agentes
asignados y cercanos que te acompañan con experiencia y
compromiso. Además, si lo necesitas, también estamos
disponibles en nuestras oficinas físicas en Alicante. Una
llamada es suficiente para conocernos.
</p>
<p className="font-semibold">
Ofrecemos una atención a medida, sin intermediarios.
Creemos que un trato personal es la única manera de
comprender a la perfección las necesidades de cada
cliente.
</p>
<p>Número de mediadora: <a href="https://dgsfp.mineco.gob.es/" className="cursor-pointer">C015723313192R</a></p>
<Chip className="flex items-center gap-2 text-md" variant="light">
Más de{" "}
<span className="text-secondary-500 font-black text-lg">
<CountUp
from={0}
to={8000}
separator=","
direction="up"
duration={1}
className="count-up-text"
/>{" "}
</span>
clientes confian en nosotras
</Chip>
<Chip className="flex items-center gap-2 text-md" variant="light">
Más de{" "}
<span className="text-secondary-500 font-black text-lg">
<CountUp
from={0}
to={400}
separator=","
direction="up"
duration={1}
className="count-up-text"
/>{" "}
</span>
seguros cerrados al mes
</Chip>
</div>
</div>
</div>
</CardBody>
<CardFooter>
<div className="flex w-full justify-end">
<Button
className="max-w-md bg-primary-300 font-semibold"
endContent={
<span className="iconify size-5 solar--alt-arrow-right-linear" />
}
onPress={() =>
navigate({
to: "/formulario",
viewTransition: true,
})
}
>
Quiero una llamada
</Button>
</div>
</CardFooter>
</Card>
<Card className="max-w-5xl" shadow="none">
<CardHeader></CardHeader>
<CardBody>
<div className="grid md:grid-cols-2 gap-4">
<div className="flex items-center justify-center overflow-hidden">
<div className="w-full min-h-[280px] relative">
<motion.div
initial={{ opacity: 0, y: 50, position: "absolute" }}
whileInView={{ opacity: 1, y: 0, position: "static" }}
transition={{ duration: 0.8, ease: "easeOut" }}
viewport={{ once: true, amount: 0.2 }}
className="w-full"
>
<Image
width={400}
height={280}
className="overflow-y-hidden"
src="https://images.unsplash.com/photo-1554331292-735256644d5f?q=80&w=2076&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
alt="victoria seguros-mayores"
/>
</motion.div>
</div>
</div>
<div>
<h1 className="text-2xl font-semibold mb-4">
Pago mensual hasta 80 años
</h1>
<div className="flex flex-col gap-2">
<p>
Entendemos que cada etapa de la vida presenta necesidades
únicas. Por ello, en Victoria Seguros, ofrecemos un plan
de pago mensual especialmente diseñado para personas
mayores de 60 años, con posibilidad de contratación
<span className="font-bold">¡hasta los 80!</span>
</p>
<p>
Este plan te permite disfrutar de la cobertura esencial
que necesitas sin sorpresas ni cargas económicas,
manteniendo tus finanzas equilibradas. Además, contarás
con el respaldo y la confianza que caracterizan a nuestra
aseguradora.
</p>
<p>
Tu tranquilidad y seguridad son nuestra prioridad, sin
importar la edad.
</p>
</div>
</div>
</div>
<div className="flex justify-center my-4">
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4 max-w-6xl w-full">
{indexCards?.map((pc) => (
<Card className="border-none" shadow="none" key={pc.title}>
<CardBody className="justify-center items-center flex flex-row md:flex-col gap-4">
<span
className={`${pc.icon} size-5 md:size-12 text-secondary/50 justify-start`}
/>
<h3 className="md:text-lg font-semibold text-sm">
{pc.title}
</h3>
</CardBody>
</Card>
))}
</div>
</div>
</CardBody>
<CardFooter className="flex flex-col gap-4">
<div className="flex w-full justify-end">
<Button
className="max-w-md bg-primary-300 font-semibold"
endContent={
<span className="iconify size-5 solar--alt-arrow-right-linear" />
}
onPress={() =>
navigate({
to: "/formulario",
viewTransition: true,
})
}
>
Quiero está oferta
</Button>
</div>
<div className="max-w-[100%]"></div>
</CardFooter>
</Card>
</div>
</div>
<Outlet />
</main>
);
}

View File

@ -0,0 +1,196 @@
import { Card, CardBody, CardFooter, CardHeader } from "@heroui/card";
import { Chip } from "@heroui/chip";
import { createFileRoute, Link } from "@tanstack/react-router";
import { Accordion, AccordionItem } from "@heroui/accordion";
import { Tabs, Tab } from "@heroui/tabs";
import { Image } from "@heroui/image";
import { Button } from "@heroui/button";
import {
dataAccordion,
dataChipDecesos,
petsCards,
servicesAccordion,
} from "~/content/decesos";
import { seo } from "~/utils/seo";
import ButtonCall from "~/components/ButtonCall";
export const Route = createFileRoute("/seguros/decesos")({
head: () => ({
meta: [
{
charSet: "utf-8",
},
{
name: "viewport",
content: "width=device-width, initial-scale=1",
},
...seo({
title: "Victoria Seguros | Decesos",
description: `Seguros de decesos en alicante`,
keywords:
"seguros, seguros en Alicante, seguros en Murcia, seguros en la Costa, seguros de decesos, seguros de vida, seguros de salud, seguro de fallecimiento, cobertura funeraria, asistencia en decesos, póliza de vida, póliza de salud, seguro médico, seguro familiar, protección financiera, seguro para extranjeros, seguro de accidentes, servicio funerario, cremación, inhumación, ataúd, seguros completos, seguros económicos, aseguradora en Alicante, aseguradora en Murcia",
}),
],
links: [
{ rel: "manifest", href: "/site.webmanifest", color: "#fffff" },
{ rel: "icon", href: "/favicon.ico" },
],
}),
component: RouteComponent,
});
function RouteComponent() {
return (
<div className="flex justify-center">
<Card className="w-full max-w-5xl m-2 md:m-4 overflow-hidden">
<CardHeader className="block">
<h1 className="text-2xl font-bold">Seguro de Decesos</h1>
</CardHeader>
<CardBody>
<div className="grid w-full md:grid-cols-3">
<div className="md:col-span-2">
<p className="">
Protege a los tuyos y disfruta de servicios exclusivos con
nuestro Plan de Asistencia Familiar, mucho más que un seguro de
decesos.
<span className="font-semibold">
Ampliamos coberturas: mascotas, asesoría legal, asistencia
dental y médica desde solo 2/mes.
</span>
</p>
<div className="flex gap-2 flex-wrap my-4">
{dataChipDecesos?.map((cp) => (
<Chip
className="bg-primary-100"
key={cp.icon}
startContent={<span className={cp.label} />}
>
{cp.icon}
</Chip>
))}
</div>
<div className="flex gap-2 justify-center mb-5">
<ButtonCall secure="decesos" />
</div>
</div>
<div className="mb-2 w-full justify-center flex items-center">
<Card className="border-none" radius="lg" shadow="none" fullWidth>
<CardBody>
<Image
alt="Manos en busca de ayuda"
className="object-cover"
src="https://images.unsplash.com/photo-1541976844346-f18aeac57b06?q=80&w=1935&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
height={250}
width={600}
/>
</CardBody>
<CardFooter className="justify-between bg-transparent overflow-hidden py-1 absolute before:rounded-xl rounded-large bottom-1 w-[calc(100%_-_8px)] ml-1 z-10">
<Button
as={Link}
variant="faded"
color="success"
to="/"
viewTransition
endContent={
<span className="iconify cib--whatsapp size-5 " />
}
>
Contactanos
</Button>
</CardFooter>
</Card>
</div>
</div>
<div className="mx-4">
<Tabs aria-label="Options" className="flex justify-center">
<Tab title="Coberturas">
<Accordion defaultExpandedKeys={["sepelio"]}>
{dataAccordion?.map((ac) => (
<AccordionItem
key={ac.key}
aria-label={ac.title}
title={ac.title}
startContent={<span className={ac.icon} />}
classNames={{
title: "font-semibold",
}}
>
<p className="mx-4">{ac?.descripcion}</p>
</AccordionItem>
))}
</Accordion>
</Tab>
<Tab title="Servicios">
<Accordion defaultExpandedKeys={["acceso-especialistas"]}>
{servicesAccordion?.map((ac) => (
<AccordionItem
key={ac.key}
aria-label={ac.title}
title={ac.title}
startContent={<span className={ac.icon} />}
classNames={{
title: "font-semibold",
}}
>
<p className="">{ac?.descripcion}</p>
</AccordionItem>
))}
</Accordion>
</Tab>
</Tabs>
</div>
</CardBody>
<CardFooter className="block">
<Card shadow="sm" className="my-8 gap-2">
<CardBody className="gap-6 grid md:grid-cols-2">
<div className="grid gap-2">
<h1 className="text-xl font-semibold">
Ahora con tu seguro de decesos también puedes incluir la
Responsabilidad Civil para perros
</h1>
<p className="text-">
Con la Asistencia a Mascotas también podrás contratar la
cobertura de Responsabilidad Civil para perros peligrosos y no
peligrosos, obligatorio con la nueva Ley de Bienestar Animal.
</p>
<div className="flex justify-center my-2">
<Button className="max-w-md" size="lg">
Solicitar
</Button>
</div>
</div>
<div className="grid place-content-center">
<Image
isZoomed
width={400}
src="https://images.unsplash.com/photo-1519052537078-e6302a4968d4?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
alt="prueba"
/>
</div>
</CardBody>
</Card>
<h1 className="text-xl mb-6 font-semibold text-purple-700 text-center">
Tu mascota es uno más de la familia
</h1>
<div className="grid md:grid-cols-4 gap-4">
{petsCards?.map((pc) => (
<Card className="border-none" shadow="none">
<CardHeader className="items-center">
<h3 className="text-lg font-semibold">{pc.title}</h3>
<span
className={`${pc.icon} size-20 absolute top-0 right-5 text-purple-500/15`}
/>
</CardHeader>
<CardBody>
<p className="text-sm">{pc.description}</p>
</CardBody>
</Card>
))}
</div>
</CardFooter>
</Card>
</div>
);
}

View File

@ -0,0 +1,244 @@
import React from "react";
import { createFileRoute } from "@tanstack/react-router";
import { Card, CardBody, CardFooter, CardHeader } from "@heroui/card";
import { Chip } from "@heroui/chip";
import { Accordion, AccordionItem } from "@heroui/accordion";
import { Tabs, Tab } from "@heroui/tabs";
import {
Table,
TableHeader,
TableColumn,
TableBody,
TableRow,
TableCell,
} from "@heroui/table";
import { homeCards, indexCards } from "~/content/hogar";
import appCss from "~/styles/app.css?url";
import { dataChipHome, accordionfrequentlyaskedquestions } from "~/content/hogar";
import ButtonCall from "~/components/ButtonCall";
import { seo } from "~/utils/seo";
type CellValue = React.ReactNode | string;
interface RowData {
key: string;
label: string;
values: CellValue[];
}
const iconCheck = (
<span className="iconify solar--shield-check-bold-duotone size-6 text-green-500" />
);
const iconCross = (
<span className="iconify solar--shield-cross-bold-duotone size-6 text-red-500" />
);
const columns = [
{ key: "a", label: "" },
{ key: "basico", label: "Básico" },
{ key: "estandar", label: "Estándar" },
{ key: "premium", label: "Premium" },
];
const rows: RowData[] = [
{ key: "incendio", label: "Incendio", values: [iconCheck, iconCheck, iconCheck] },
{ key: "servicio-asistencia", label: "Servicio de asistencia", values: [iconCheck, iconCheck, iconCheck] },
{ key: "responsabilidad-civil", label: "Responsabilidad civil", values: ["Solo incendio", iconCheck, iconCheck] },
{ key: "alimento-refrigerados", label: "Alimentos refrigerados", values: [iconCross, iconCheck, iconCheck] },
{ key: "daños-por-agua", label: "Daños por agua", values: [iconCross, iconCheck, iconCheck] },
{ key: "roturas", label: "Roturas", values: [iconCross, iconCheck, iconCheck] },
{ key: "robo-y-vandalismo", label: "Robo y vandalismo", values: [iconCross, iconCheck, iconCheck] },
{ key: "coberturas-consecuenciales", label: "Coberturas consecuenciales", values: [iconCross, iconCheck, iconCheck] },
{ key: "defensa-jurídica", label: "Defensa jurídica", values: [iconCross, iconCheck, iconCheck] },
{ key: "robo-integral", label: "Robo Integral", values: [iconCross, iconCross, iconCheck] },
{ key: "daños-eléctricos", label: "Daños eléctricos", values: [iconCross, iconCross, iconCheck] },
{ key: "bricohogar", label: "Bricohogar", values: [iconCross, iconCross, iconCheck] },
];
const optionalRows: RowData[] = [
{ key: "accidentes", label: "Accidentes", values: [iconCross, "Opcional", "Opcional"] },
{ key: "mascotas", label: "Mascotas", values: [iconCross, "Opcional", "Opcional"] },
{ key: "vehículos-y-motos-en-garaje", label: "Vehículos y motos en garaje", values: [iconCross, "Opcional", "Opcional"] },
{ key: "robo-objetos-valor-especial", label: "Robo de objetos de valor especial", values: [iconCross, iconCross, "Opcional"] },
];
function renderRows(rowsData: RowData[]) {
return rowsData.map(({ key, label, values }) => (
<TableRow key={key}>
<TableCell className="font-semibold">{label}</TableCell>
{values.map((val, i) => (
<TableCell key={i}>{val}</TableCell>
))}
</TableRow>
));
}
// Tabla responsive para móviles (simple versión vertical)
function MobileTable({ rowsData }: { rowsData: RowData[] }) {
return (
<div className="space-y-4">
{rowsData.map(({ key, label, values }) => (
<div
key={key}
role="table"
aria-label={label}
className="border border-gray-300 rounded-lg p-4 shadow-sm bg-white"
>
<div className="font-semibold mb-4 text-gray-800 text-lg border-b border-gray-200 pb-2">
{label}
</div>
<div className="grid grid-cols-3 gap-4 text-center text-sm">
{columns.slice(1).map((col, i) => (
<div
key={col.key}
className="flex flex-col items-center bg-gray-50 rounded-md p-3 hover:bg-gray-100 transition"
>
<span className="font-semibold mb-2 text-gray-700">{col.label}</span>
<span className="text-gray-900">{values[i]}</span>
</div>
))}
</div>
</div>
))}
</div>
);
}
function RouteComponent() {
// Detectamos ancho de pantalla para mostrar tabla normal o mobile
// Aquí por simplicidad usamos un hook, en caso real usaría react-responsive o css para ocultar/mostrar.
const [isMobile, setIsMobile] = React.useState(false);
React.useEffect(() => {
const onResize = () => setIsMobile(window.innerWidth < 640);
onResize();
window.addEventListener("resize", onResize);
return () => window.removeEventListener("resize", onResize);
}, []);
return (
<div className="flex justify-center">
<Card className="w-full max-w-5xl m-4 overflow-hidden">
<CardHeader className="block">
<h1 className="text-2xl font-bold">Seguro de Hogar</h1>
<div className="flex flex-wrap gap-2 my-4">
{dataChipHome?.map((cp) => (
<Chip
className="bg-primary-100"
key={cp.icon}
startContent={<span className={cp.label} />}
>
{cp.icon}
</Chip>
))}
</div>
</CardHeader>
<CardBody>
<p className="mx-4">
El Seguro de Hogar te permite asegurar lo que realmente necesitas.{" "}
<span className="font-semibold ">
Porque no todas las viviendas son iguales, ni todas las familias
tienen las mismas necesidades.
</span>
</p>
<div className="flex gap-2 justify-center mb-5">
<ButtonCall secure="hogar" />
</div>
<div className="mx-4">
<Tabs aria-label="Options" className="flex justify-center">
<Tab title="Coberturas">
{isMobile ? (
<MobileTable rowsData={rows} />
) : (
<Table aria-label="Tabla de coberturas">
<TableHeader>
{columns.map((column) => (
<TableColumn key={column.key}>{column.label}</TableColumn>
))}
</TableHeader>
<TableBody>{renderRows(rows)}</TableBody>
</Table>
)}
</Tab>
<Tab title="Coberturas Opcionales">
{isMobile ? (
<MobileTable rowsData={optionalRows} />
) : (
<Table aria-label="Tabla de coberturas opcionales">
<TableHeader>
{columns.map((column) => (
<TableColumn key={column.key}>{column.label}</TableColumn>
))}
</TableHeader>
<TableBody>{renderRows(optionalRows)}</TableBody>
</Table>
)}
</Tab>
</Tabs>
</div>
</CardBody>
<CardFooter className="block">
<h1 className="text-xl mb-6 font-semibold text-purple-700 text-center">
Te prestamos servicio las 24 horas del día
</h1>
<div className="grid md:grid-cols-4 gap-4">
{homeCards?.map((pc) => (
<Card key={pc.title} className="border-none" shadow="none">
<CardHeader className="items-center relative">
<h3 className="text-lg font-semibold">{pc.title}</h3>
<span
className={`${pc.icon} size-20 absolute top-0 right-5 text-purple-500/15`}
/>
</CardHeader>
<CardBody>
<p className="text-sm">{pc.description}</p>
</CardBody>
</Card>
))}
</div>
<h1 className="text-xl mb-6 font-semibold text-purple-700 text-center">
Preguntas frecuentes
</h1>
<Accordion>
{accordionfrequentlyaskedquestions?.map((pf) => (
<AccordionItem
key={pf.title}
title={pf.title}
classNames={{ title: "font-semibold" }}
>
{pf.description}
</AccordionItem>
))}
</Accordion>
</CardFooter>
</Card>
</div>
);
}
export const Route = createFileRoute("/seguros/hogar")({
head: () => ({
meta: [
{ charSet: "utf-8" },
{ name: "viewport", content: "width=device-width, initial-scale=1" },
...seo({
title: "Victoria Seguros | Tú aseguradora de confianza",
description: `La aseguradora n1 de Alicante y Costa del Sol`,
keywords:
"seguros, seguros en Alicante, seguros en Murcia, seguros en la Costa, seguros de decesos, seguros de vida, seguros de salud, seguro de fallecimiento, cobertura funeraria, asistencia en decesos, póliza de vida, póliza de salud, seguro médico, seguro familiar, protección financiera, seguro para extranjeros, seguro de accidentes, servicio funerario, cremación, inhumación, ataúd, seguros completos, seguros económicos, aseguradora en Alicante, aseguradora en Murcia",
}),
],
links: [
{ rel: "stylesheet", href: appCss },
{ rel: "manifest", href: "/site.webmanifest", color: "#fffff" },
{ rel: "icon", href: "/favicon.ico" },
],
}),
component: RouteComponent,
});

View File

@ -0,0 +1,95 @@
import { Card, CardBody, CardFooter, CardHeader } from "@heroui/card";
import { Chip } from "@heroui/chip";
import { createFileRoute } from "@tanstack/react-router";
import { Accordion, AccordionItem } from "@heroui/accordion";
import { Tabs, Tab } from "@heroui/tabs";
import appCss from "~/styles/app.css?url";
import { dataChipCaballos, dataAccordionCaballos } from "~/content/perros";
import ButtonCall from "~/components/ButtonCall";
import { seo } from "~/utils/seo";
export const Route = createFileRoute("/seguros/mascotas/caballos")({
head: () => ({
meta: [
{
charSet: "utf-8",
},
{
name: "viewport",
content: "width=device-width, initial-scale=1",
},
...seo({
title: "Victoria Seguros | Tú aseguradora de confianza",
description: `La aseguradora n1 de Alicante y Costa del Sol`,
keywords:
"seguros, seguros en Alicante, seguros en Murcia, seguros en la Costa, seguros de decesos, seguros de vida, seguros de salud, seguro de fallecimiento, cobertura funeraria, asistencia en decesos, póliza de vida, póliza de salud, seguro médico, seguro familiar, protección financiera, seguro para extranjeros, seguro de accidentes, servicio funerario, cremación, inhumación, ataúd, seguros completos, seguros económicos, aseguradora en Alicante, aseguradora en Murcia",
}),
],
links: [
{ rel: "stylesheet", href: appCss },
{ rel: "manifest", href: "/site.webmanifest", color: "#fffff" },
{ rel: "icon", href: "/favicon.ico" },
],
}),
component: RouteComponent,
});
function RouteComponent() {
return (
<div className="flex justify-center">
<Card className="w-full max-w-5xl m-2 md:m-4 overflow-hidden">
<CardHeader className="block">
<h1 className="text-2xl font-bold">Seguro de Caballos</h1>
<div className="flex gap-2 flex-wrap my-4">
{dataChipCaballos?.map((cp) => (
<Chip
className="bg-primary-100"
key={cp.icon}
startContent={<span className={cp.label} />}
>
{cp.icon}
</Chip>
))}
</div>
</CardHeader>
<CardBody>
<p className="font-semibold text-sm mx-4">
Asegura tu tranquilidad con el seguro de Responsabilidad Civil para
perros, que te proporcionará protección en caso de daños a terceros.
</p>
<div className="flex gap-2n justify-center mb-5">
<ButtonCall secure="caballos"/>
</div>
<div className="mx-4">
<h1 className="text-xl mb-6 font-semibold text-purple-700 text-center">
¿Por qué contratar el Seguro de Caballos de Victoria Seguros?
</h1>
<Tabs aria-label="Options" className="flex justify-center">
<Tab title="Coberturas">
<Accordion defaultExpandedKeys={["sepelio"]}>
{dataAccordionCaballos?.map((ac) => (
<AccordionItem
key={ac.key}
aria-label={ac.title}
title={ac.title}
startContent={<span className={ac.icon} />}
classNames={{
title: "font-semibold",
}}
>
<p className="">{ac?.descripcion}</p>
</AccordionItem>
))}
</Accordion>
</Tab>
</Tabs>
</div>
</CardBody>
<CardFooter className="block"></CardFooter>
</Card>
</div>
);
}

View File

@ -0,0 +1,72 @@
import { createFileRoute, Link } from "@tanstack/react-router";
import { Button } from "@heroui/button";
import { Card, CardBody, CardFooter, CardHeader } from "@heroui/card";
import appCss from "~/styles/app.css?url";
import { seo } from "~/utils/seo";
import { Image } from "@heroui/image";
export const Route = createFileRoute("/seguros/mascotas/")({
head: () => ({
meta: [
{
charSet: "utf-8",
},
{
name: "viewport",
content: "width=device-width, initial-scale=1",
},
...seo({
title: "Victoria Seguros | Tú aseguradora de confianza",
description: `La aseguradora n1 de Alicante y Costa del Sol`,
keywords:
"seguros, seguros en Alicante, seguros en Murcia, seguros en la Costa, seguros de decesos, seguros de vida, seguros de salud, seguro de fallecimiento, cobertura funeraria, asistencia en decesos, póliza de vida, póliza de salud, seguro médico, seguro familiar, protección financiera, seguro para extranjeros, seguro de accidentes, servicio funerario, cremación, inhumación, ataúd, seguros completos, seguros económicos, aseguradora en Alicante, aseguradora en Murcia",
}),
],
links: [
{ rel: "stylesheet", href: appCss },
{ rel: "manifest", href: "/site.webmanifest", color: "#fffff" },
{ rel: "icon", href: "/favicon.ico" },
],
}),
component: RouteComponent,
});
function RouteComponent() {
return (
<div className="flex justify-center items-center">
<div className="grid max-w-5xl md:grid-cols-2 gap-4 my-5 mx-4">
<Card className="border-none" radius="lg">
<Image
alt="Woman listing to music"
className="object-cover"
height={300}
src="https://images.unsplash.com/photo-1588943211346-0908a1fb0b01?q=80&w=1935&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
width={300}
/>
<CardFooter className="justify-between bg-white border-white/20 border-1 overflow-hidden py-1 absolute before:rounded-xl rounded-large bottom-1 w-[calc(100%_-_8px)] shadow-small ml-1 z-10">
<p className="font-semibold">Seguros de Perros</p>
<Button as={Link} color="secondary" to="perros" viewTransition>
Ver más
</Button>
</CardFooter>
</Card>
<Card className="border-none" radius="lg">
<Image
alt="Woman listing to music"
className="object-cover"
height={300}
src="https://images.unsplash.com/uploads/14136148007774dc82563/ce92d553?q=80&w=1946&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
width={300}
/>
<CardFooter className="justify-between bg-white border-white/20 border-1 overflow-hidden py-1 absolute before:rounded-xl rounded-large bottom-1 w-[calc(100%_-_8px)] shadow-small ml-1 z-10">
<p className="font-semibold">Seguro de Caballos</p>
<Button as={Link} color="secondary" to="caballos" viewTransition>
Ver más
</Button>
</CardFooter>
</Card>
</div>
</div>
);
}

View File

@ -0,0 +1,203 @@
import { Card, CardBody, CardFooter, CardHeader } from "@heroui/card";
import { Chip } from "@heroui/chip";
import { createFileRoute } from "@tanstack/react-router";
import { Accordion, AccordionItem } from "@heroui/accordion";
import { Tabs, Tab } from "@heroui/tabs";
import { Image } from "@heroui/image";
import { Button } from "@heroui/button";
import appCss from "~/styles/app.css?url";
import { dataChip, dataAccordion, petsCards } from "~/content/perros";
import ButtonCall from "~/components/ButtonCall";
import { seo } from "~/utils/seo";
export const Route = createFileRoute("/seguros/mascotas/perros")({
head: () => ({
meta: [
{
charSet: "utf-8",
},
{
name: "viewport",
content: "width=device-width, initial-scale=1",
},
...seo({
title: "Victoria Seguros | Tú aseguradora de confianza",
description: `La aseguradora n1 de Alicante y Costa del Sol`,
keywords:
"seguros, seguros en Alicante, seguros en Murcia, seguros en la Costa, seguros de decesos, seguros de vida, seguros de salud, seguro de fallecimiento, cobertura funeraria, asistencia en decesos, póliza de vida, póliza de salud, seguro médico, seguro familiar, protección financiera, seguro para extranjeros, seguro de accidentes, servicio funerario, cremación, inhumación, ataúd, seguros completos, seguros económicos, aseguradora en Alicante, aseguradora en Murcia",
}),
],
links: [
{ rel: "stylesheet", href: appCss },
{ rel: "manifest", href: "/site.webmanifest", color: "#fffff" },
{ rel: "icon", href: "/favicon.ico" },
],
}),
component: RouteComponent,
});
function RouteComponent() {
return (
<div className="flex justify-center">
<Card className="w-full max-w-5xl m-2 md:m-4 overflow-hidden">
<CardHeader className="block">
<h1 className="text-2xl font-bold">Seguro de Perros</h1>
<div className="flex gap-2 flex-wrap my-4">
{dataChip?.map((cp) => (
<Chip
className="bg-primary-100"
key={cp.icon}
startContent={<span className={cp.label} />}
>
{cp.icon}
</Chip>
))}
</div>
</CardHeader>
<CardBody>
<p className="font-semibold text-sm mx-4">
Asegura tu tranquilidad con el seguro de Responsabilidad Civil para
perros, que te proporcionará protección en caso de daños a terceros.
</p>
<div className="flex gap-2 mb-5 justify-center">
<ButtonCall secure="perros"/>
</div>
<div className="grid md:grid-cols-2 gap-2 mb-10 mt-5">
<Card>
<CardBody>
{" "}
<div className="flex justify-center">
<Image
isZoomed
width={300}
height={200}
src="https://images.unsplash.com/photo-1561037404-61cd46aa615b?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
alt="prueba"
className="max-w-md self-center"
/>
</div>
<p className="font-bold text-sm absolute z-20 h-24 w-24 rounded-full bg-secondary text-default-50 p-1 flex text-center items-center translate-x-5">
Desde 52/año
</p>
<p className="font-semibold text-xl text-center">
Seguro de perro
</p>
</CardBody>
<CardFooter>
<p className="text-sm">
El seguro de perro obligatorio para razas no peligrosas:
Labrador, Golden Retriever, Bulldog Francés, Bichón Maltés,
Pastor Alemán, Beagle, Setter Inglés, Caniche, Yorkshire,
Schnauzer, Chihuahua..., entre otras.
</p>
</CardFooter>
</Card>
<Card>
<CardBody className="">
<div className="flex justify-center">
<Image
isZoomed
width={300}
height={200}
src="https://images.unsplash.com/photo-1600369671738-fa3a43efeced?q=80&w=1964&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
alt="prueba"
className="max-w-md"
/>
</div>
<p className="font-bold text-sm absolute z-20 h-24 w-24 rounded-full bg-secondary text-default-50 p-1 flex text-center items-center translate-x-5">
Desde 105/año
</p>
<p className="font-semibold text-xl text-center">
Seguro de perro peligroso
</p>
</CardBody>
<CardFooter>
<p className="text-sm">
El seguro de perro obligatorio para razas peligrosas: Pit Bull
Terrier, Rottweiler, Dogo Argentino, American Staffordshire,
Fila Brasileño, Tosa Inu, Akira Inu, Presa Canario,
Bullmastiff..., entre otras.
</p>
</CardFooter>
</Card>
</div>
<div className="mx-4">
<h1 className="text-xl mb-6 font-semibold text-purple-700 text-center">
Coberturas del seguro de Responsabilidad Civil para perros
</h1>
<Tabs aria-label="Options" className="flex justify-center">
<Tab title="Coberturas">
<Accordion defaultExpandedKeys={["sepelio"]}>
{dataAccordion?.map((ac) => (
<AccordionItem
key={ac.key}
aria-label={ac.title}
title={ac.title}
startContent={<span className={ac.icon} />}
classNames={{
title: "font-semibold",
}}
>
<p className="">{ac?.descripcion}</p>
</AccordionItem>
))}
</Accordion>
</Tab>
</Tabs>
</div>
</CardBody>
<CardFooter className="block">
<h1 className="text-xl mb-6 font-semibold text-purple-700 text-center">
Ventajas del seguro de Responsabilidad Civil para perros
</h1>
<div className="grid md:grid-cols-4 gap-4">
{petsCards?.map((pc) => (
<Card className="border-none" shadow="none">
<CardHeader className="items-center">
<h3 className="text-lg font-semibold">{pc.title}</h3>
<span
className={`${pc.icon} size-20 absolute top-0 right-5 text-purple-500/15`}
/>
</CardHeader>
<CardBody>
<p className="text-sm">{pc.description}</p>
</CardBody>
</Card>
))}
</div>
<Card shadow="sm" className="my-8 gap-2">
<CardBody className="gap-6 grid md:grid-cols-2">
<div className="grid gap-2">
<h1 className="text-xl font-semibold">
Ahora con tu seguro de decesos también puedes incluir la
Responsabilidad Civil para perros
</h1>
<p className="text-">
Con la Asistencia a Mascotas también podrás contratar la
cobertura de Responsabilidad Civil para perros peligrosos y no
peligrosos, obligatorio con la nueva Ley de Bienestar Animal.
</p>
<div className="flex justify-center my-2">
<Button className="max-w-md" size="lg">
Solicitar
</Button>
</div>
</div>
<div className="grid place-content-center">
<Image
isZoomed
width={400}
src="https://images.unsplash.com/photo-1519052537078-e6302a4968d4?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
alt="prueba"
/>
</div>
</CardBody>
</Card>
</CardFooter>
</Card>
</div>
);
}

View File

@ -0,0 +1,366 @@
import { createFileRoute, Link } from "@tanstack/react-router";
import { Card, CardBody, CardFooter, CardHeader } from "@heroui/card";
import { Chip } from "@heroui/chip";
import { Accordion, AccordionItem } from "@heroui/accordion";
import { Tabs, Tab } from "@heroui/tabs";
import { Image } from "@heroui/image";
import { Button } from "@heroui/button";
import { servicesAccordion } from "~/content/decesos";
import appCss from "~/styles/app.css?url";
import { dataCardDemo, dataCardHealth, dataChipHealth } from "~/content/salud";
import ButtonCall from "~/components/ButtonCall";
import { seo } from "~/utils/seo";
export const Route = createFileRoute("/seguros/salud")({
head: () => ({
meta: [
{
charSet: "utf-8",
},
{
name: "viewport",
content: "width=device-width, initial-scale=1",
},
...seo({
title: "Victoria Seguros | Tú aseguradora de confianza",
description: `La aseguradora n1 de Alicante y Costa del Sol `,
}),
],
links: [
{ rel: "stylesheet", href: appCss },
{ rel: "manifest", href: "/site.webmanifest", color: "#fffff" },
{ rel: "icon", href: "/favicon.ico" },
],
}),
component: RouteComponent,
});
function RouteComponent() {
return (
<div className="flex justify-center">
<Card className="w-full max-w-5xl m-2 md:m-4 overflow-hidden">
<CardHeader className="block">
<h1 className="text-2xl font-bold">Seguro de Salud</h1>
<div className="flex gap-2 flex-wrap my-4">
{dataChipHealth?.map((cp) => (
<Chip
className="bg-primary-100"
key={cp.icon}
startContent={<span className={cp.label} />}
>
{cp.icon}
</Chip>
))}
</div>
</CardHeader>
<CardBody className="flex flex-col">
<div className="mb-5">
<p className="font-semibold mx-4">
Hay cosas que cuando llegan, te cambian la vida, como el seguro de
Salud Integral de Helvetia. Un seguro de Salud COMPLETO y SIN COPAGOS
hasta el 11º uso.
</p>
<div className="flex gap-2 justify-center mb-5">
<ButtonCall secure="salud" />
</div>
</div>
<div className="grid md:grid-cols-4 gap-4 my-4">
{dataCardDemo?.map((pc) => (
<Card className="border-none" shadow="none">
<CardHeader className="items-center">
<h3 className="text-lg font-semibold">{pc.title}</h3>
<span
className={`${pc.icon} size-20 absolute top-0 right-5 text-purple-500/15`}
/>
</CardHeader>
<CardBody>
<p className="text-sm">{pc.description}</p>
</CardBody>
</Card>
))}
</div>
<div className="mx-4">
<Tabs aria-label="Options" className="flex justify-center">
<Tab title="Coberturas">
<Accordion defaultExpandedKeys={["cp"]}>
<AccordionItem
key={"cp"}
aria-label={"Coberturas principales"}
title={"Coberturas principales"}
startContent={
<span
className={"iconify solar--jar-of-pills-2-line-duotone"}
/>
}
classNames={{
title: "font-semibold",
}}
>
<ul className="">
<li>- Medicina general</li>
<li>- Urgencias hospitalarias </li>
<li>- Urgencias domiciliarias</li>
<li>- Especialidades médicas</li>
<li>- Asistencia médica 24 horas</li>
<li>- Pruebas diagnósticas</li>
<li>- Asistencia médica en el extranjero</li>
<li>- Hospitalización</li>
<li>- Segunda opinión médica</li>
<li>- Complemento bucodental</li>
<li>- Prótesis internas</li>
</ul>
</AccordionItem>
<AccordionItem
key={"te"}
aria-label={"Tratamientos especiales"}
title={"Tratamientos especiales"}
startContent={
<span
className={"iconify solar--dropper-2-line-duotone"}
/>
}
classNames={{
title: "font-semibold",
}}
>
<ul className="">
<li>- Psicología </li>
<li>- Podología </li>
<li>
- Otros tratamientos especiales (oxigenoterapia,
laserterapia, litroticia extracorpórea renal, diálisis,
quimioterapia, radioterapia...)
</li>
<li>
- Dermatoscopia digital - detección precoz del melanoma
</li>
<li>- Dianas terapéuticas - tratamiento oncológico</li>
<li>
- Cirugía robotizada en intervenciones de cáncer de
próstata con el sistema Da Vinci
</li>
<li>- Cirugía de la mama sana</li>
<li>- Cirugía esófago de Barret</li>
<li>- Rehabilitación cardiaca tras infarto</li>
<li>- Estudio biomecánico de la pisada</li>
<li>- Rehabilitación del suelo pélvico </li>
<li>
- Rehabilitación vestibular para patología de oído
interno
</li>
</ul>
</AccordionItem>
<AccordionItem
key={"ep"}
aria-label={"Embarazo y planificación familiar"}
title={"Embarazo y planificación familiar"}
startContent={
<span className={"iconify solar--dna-line-duotone"} />
}
classNames={{
title: "font-semibold",
}}
>
<ul className="">
<li>- Diagnóstico y tratamiento de la infertilidad</li>{" "}
<li>- Programa de preparación al parto</li>{" "}
<li>- Test de cribado prenatal no invasivo </li>
<li>- Cuidados posparto</li>{" "}
<li>- Planificación familiar</li>
</ul>
</AccordionItem>
<AccordionItem
key={"tm"}
aria-label={"Telemedicina"}
title={"Telemedicina"}
startContent={
<span
className={"iconify solar--videocamera-line-duotone"}
/>
}
classNames={{
title: "font-semibold",
}}
>
<ul className="">
<li>- Llamadas, videollamadas, chat médico online </li>
<li>- Sin copagos y sin límite de consultas </li>
<li>- Medicina general y 29 especialidades </li>
<li>- Prescripciones para pruebas y medicamentos</li>
<li>
- Selfie health: medidor de indicadores de salud desde
tu móvil (tensión, frecuencia cardiaca, estrés...)
</li>
</ul>
</AccordionItem>
</Accordion>
</Tab>
<Tab title="Servicios">
<Accordion defaultExpandedKeys={["acceso-especialistas"]}>
{servicesAccordion?.map((ac) => (
<AccordionItem
key={ac.key}
aria-label={ac.title}
title={ac.title}
startContent={<span className={ac.icon} />}
classNames={{
title: "font-semibold",
}}
>
<p className="">{ac?.descripcion}</p>
</AccordionItem>
))}
</Accordion>
</Tab>
</Tabs>
</div>
</CardBody>
<CardFooter className="block">
<Card shadow="none" className="my-8 gap-2 border-none">
<CardBody className="gap-6 grid ">
<div>
<h1 className="text-xl mb-6 font-semibold text-purple-700 text-center">
Precios y descuentos exclusivos
</h1>
<h2 className="text-lg mb-6 font-semibold text-gray-700 text-center">
Te ayudamos con tus gastos médicos mediante diferentes
reembolsos.
</h2>
</div>
<div className="grid md:grid-cols-4 gap-4">
{dataCardHealth?.map((pc) => (
<Card className="border-none" shadow="none">
<CardHeader className="items-center">
<h3 className="text-lg font-semibold">{pc.title}</h3>
<span
className={`${pc.icon} size-20 absolute top-0 right-5 text-purple-500/15`}
/>
</CardHeader>
<CardBody>
<p className="text-sm">{pc.description}</p>
</CardBody>
</Card>
))}
</div>
</CardBody>
</Card>
<Card shadow="sm" className="my-8 gap-2">
<CardBody className="gap-6 grid md:grid-cols-2">
<div className="grid gap-2">
<h1 className="text-xl font-semibold">
Plataforma Mi Bienestar Emocional
</h1>
<p className="text-">
Mejora el cuidado de tu mente y tus emociones con la
Plataforma Mi Bienestar Emocional. Herramienta online, con
sesiones en directo, guiada por profesionales en el campo de
la psicología. Con ella aprenderás de forma rápida y sencilla,
diferentes rutinas y hábitos a incorporar en tu día a día para
mejorar el cuidado de tu mente y emociones.
</p>
</div>
<div className="grid place-content-center">
<Image
isZoomed
width={400}
height={300}
src="https://images.unsplash.com/photo-1482100199117-a4a38a64e7e3?q=80&w=2069&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
alt="prueba"
/>
</div>
</CardBody>
</Card>
<Card shadow="sm" className="my-8 gap-2">
<CardBody className="gap-6 grid md:grid-cols-2">
<div className="grid place-content-center">
<Image
isZoomed
width={400}
height={300}
src="https://images.unsplash.com/photo-1554244933-d876deb6b2ff?q=80&w=2080&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
alt="prueba"
/>
</div>
<div className="grid gap-2">
<h1 className="text-xl font-semibold">
Plataforma Caser Más Beneficios
</h1>
<p className="text-">
Encontrarás servicios orientados a la prevención y el
bienestar de tu salud, y también, al mantenimiento del hogar.
¿El objetivo? Ayudarte en tu día a día con servicios
complementarios al seguro. Todos nuestros servicios se llevan
a cabo en clínicas de prestigio y las mejores empresas en cada
sector, para que siempre recibas una atención personalizada y
de calidad
</p>
</div>
</CardBody>
</Card>
<Card shadow="sm" className="my-8 gap-2">
<CardBody className="gap-6 grid md:grid-cols-2">
<div className="grid gap-2">
<h1 className="text-xl font-semibold">
Descárgate nuestra app Helvetia Salud
</h1>
<p className="text-">
La app con la que gestionar todo lo relacionado con tu seguro
de salud. Porque hacer uso de tu seguro nunca fue tan simple y
rápido. Desde cualquier dispositivo y estés donde estés,
innovación digital para facilitar y mejorar tu día a día.
</p>
<ol>
<li>
Consulta el Cuadro Médico para encontrar al especialista más
cercano.{" "}
</li>
<li>
Solicita y consulta tus autorizaciones médicas y reembolsos.
</li>
<li>
Utiliza tu tarjeta digital para identificarte en los centros
médicos.{" "}
</li>
<li>
Otros servicios como: Centro médico telemedicina, Caser
+Beneficios, promociones...{" "}
</li>
</ol>
<Button
as={Link}
to="https://apps.apple.com/us/app/victoria seguros-salud/id6469604635"
startContent={
<span className="iconify size-6 cib--app-store" />
}
color="secondary"
>
AppStore - Apple
</Button>
<Button
as={Link}
to="https://play.google.com/store/apps/details?id=com.caser.victoria seguros.salud"
startContent={
<span className="iconify size-6 cib--google-play" />
}
color="secondary"
>
PlayStore - Android
</Button>
</div>
<div className="grid place-content-center">
<Image
isZoomed
width={400}
height={300}
src="https://images.unsplash.com/photo-1522125670776-3c7abb882bc2?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
alt="prueba"
/>
</div>
</CardBody>
</Card>
</CardFooter>
</Card>
</div>
);
}

View File

@ -0,0 +1,84 @@
import { Card, CardBody, CardFooter, CardHeader } from "@heroui/card";
import { Chip } from "@heroui/chip";
import { createFileRoute, Link } from "@tanstack/react-router";
import { Accordion, AccordionItem } from "@heroui/accordion";
import { Tabs, Tab } from "@heroui/tabs";
import { Image } from "@heroui/image";
import { Button } from "@heroui/button";
import {
dataAccordion,
dataChipDecesos,
petsCards,
servicesAccordion,
} from "~/content/decesos";
import { seo } from "~/utils/seo";
import ButtonCall from "~/components/ButtonCall";
import { dataChipCoche } from "~/content/coche";
export const Route = createFileRoute("/seguros/vehiculos/coche")({
head: () => ({
meta: [
{
charSet: "utf-8",
},
{
name: "viewport",
content: "width=device-width, initial-scale=1",
},
...seo({
title: "Victoria Seguros | Decesos",
description: `Seguros de decesos en alicante`,
keywords:
"seguros, seguros en Alicante, vehículos, coche, coches, motos, 4x4, camiones, alicante, seguros, seguros en Murcia, seguros en la Costa, seguros de decesos, seguros de vida, seguros de salud, seguro de fallecimiento, cobertura funeraria, asistencia en decesos, póliza de vida, póliza de salud, seguro médico, seguro familiar, protección financiera, seguro para extranjeros, seguro de accidentes, servicio funerario, cremación, inhumación, ataúd, seguros completos, seguros económicos, aseguradora en Alicante, aseguradora en Murcia",
}),
],
links: [
{ rel: "manifest", href: "/site.webmanifest", color: "#fffff" },
{ rel: "icon", href: "/favicon.ico" },
],
}),
component: RouteComponent,
});
function RouteComponent() {
return (
<div className="flex justify-center">
<Card className="w-full max-w-5xl m-2 md:m-4 overflow-hidden">
<CardHeader className="block">
<h1 className="text-2xl font-bold">Seguro de coches</h1>
</CardHeader>
<CardBody>
<div className="grid w-full md:grid-cols-3">
<div className="md:col-span-2">
<p className="">
Existen muchas opciones de seguros de auto, pero pocas son tan
completas como el que te ofrece Victoria Seguros. Nuestra póliza
se adapta a tus necesidades y a las de tu vehículo, brindándote
la protección que realmente importa.
<span className="font-semibold">
{" "}
Descubre todas nuestras coberturas y elige la que mejor se
adapte a tus necesidades.
</span>
</p>
<div className="flex gap-2 flex-wrap my-4">
{dataChipCoche?.map((cp) => (
<Chip
className="bg-primary-100"
key={cp.icon}
startContent={<span className={cp.label} />}
>
{cp.icon}
</Chip>
))}
</div>
<div className="flex gap-2 justify-center mb-5">
<ButtonCall secure="coche" />
</div>
</div>
</div>
</CardBody>
</Card>
</div>
);
}

View File

@ -0,0 +1,49 @@
import { Button } from "@heroui/button";
import { Card, CardBody, CardFooter, CardHeader } from "@heroui/card";
import { createFileRoute, Link } from "@tanstack/react-router";
import { Image } from "@heroui/image";
export const Route = createFileRoute("/seguros/vehiculos/")({
component: RouteComponent,
});
function RouteComponent() {
return (
<>
<div className="flex justify-center">
<div className="grid max-w-5xl sm:grid-cols-2 gap-4 my-5 mx-4">
<Card className="border-none" radius="lg">
<Image
alt="Woman listing to music"
className="object-cover"
height={300}
src="https://images.unsplash.com/photo-1611448746128-7c39e03b71e4?q=80&w=1949&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
width={300}
/>
<CardFooter className="justify-between bg-white border-white/20 border-1 overflow-hidden py-1 absolute before:rounded-xl rounded-large bottom-1 w-[calc(100%_-_8px)] shadow-small ml-1 z-10">
<p className="font-semibold">Seguros de Coche</p>
<Button as={Link} color="secondary" to="coche" viewTransition>
Ver más
</Button>
</CardFooter>
</Card>
<Card className="border-none" radius="lg">
<Image
alt="Woman listing to music"
className="object-cover"
height={300}
src="https://images.unsplash.com/photo-1558981806-ec527fa84c39?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
width={300}
/>
<CardFooter className="justify-between bg-white border-white/20 border-1 overflow-hidden py-1 absolute before:rounded-xl rounded-large bottom-1 w-[calc(100%_-_8px)] shadow-small ml-1 z-10">
<p className="font-semibold">Seguro de Motos</p>
<Button as={Link} color="secondary" to="moto" viewTransition>
Ver más
</Button>
</CardFooter>
</Card>
</div>
</div>
</>
);
}

View File

@ -0,0 +1,77 @@
import { Card, CardBody, CardHeader } from "@heroui/card";
import { Chip } from "@heroui/chip";
import { createFileRoute } from "@tanstack/react-router";
import { seo } from "~/utils/seo";
import ButtonCall from "~/components/ButtonCall";
import { dataChipMoto } from "~/content/coche";
export const Route = createFileRoute("/seguros/vehiculos/moto")({
head: () => ({
meta: [
{
charSet: "utf-8",
},
{
name: "viewport",
content: "width=device-width, initial-scale=1",
},
...seo({
title: "Victoria Seguros | Decesos",
description: `Seguros de decesos en alicante`,
keywords:
"seguros, seguros en Alicante, vehículos, coche, coches, motos, 4x4, camiones, alicante, seguros, seguros en Murcia, seguros en la Costa, seguros de decesos, seguros de vida, seguros de salud, seguro de fallecimiento, cobertura funeraria, asistencia en decesos, póliza de vida, póliza de salud, seguro médico, seguro familiar, protección financiera, seguro para extranjeros, seguro de accidentes, servicio funerario, cremación, inhumación, ataúd, seguros completos, seguros económicos, aseguradora en Alicante, aseguradora en Murcia",
}),
],
links: [
{ rel: "manifest", href: "/site.webmanifest", color: "#fffff" },
{ rel: "icon", href: "/favicon.ico" },
],
}),
component: RouteComponent,
});
function RouteComponent() {
return (
<div className="flex justify-center">
<Card className="w-full max-w-5xl m-2 md:m-4 overflow-hidden">
<CardHeader className="block">
<h1 className="text-2xl font-bold">Seguro de motos</h1>
</CardHeader>
<CardBody>
<div className="grid w-full md:grid-cols-3">
<div className="md:col-span-2">
<p className="">
Conduce tu motocicleta o ciclomotor con total confianza gracias
al Seguro de Moto de Victoria Seguros: una cobertura a medida
para todos los apasionados de las dos ruedas.
<span className="font-semibold">
{" "}
La seguridad y la economía pueden ir de la mano. Si tu
motocicleta tiene algunos años o la usas con poca frecuencia,
un seguro a terceros es ideal. ¿Buscas algo más? El seguro a
terceros ampliado incluye cobertura contra robo e incendio. Y
si tienes una moto nueva y deseas la máxima protección, el
seguro a todo riesgo con deducible es tu mejor opción.
</span>
</p>
<div className="flex gap-2 flex-wrap my-4">
{dataChipMoto?.map((cp) => (
<Chip
className="bg-primary-100"
key={cp.icon}
startContent={<span className={cp.label} />}
>
{cp.icon}
</Chip>
))}
</div>
<div className="flex gap-2 justify-center mb-5">
<ButtonCall secure="motos" />
</div>
</div>
</div>
</CardBody>
</Card>
</div>
);
}

270
src/routes/seguros/vida.tsx Normal file
View File

@ -0,0 +1,270 @@
import { Card, CardBody, CardFooter, CardHeader } from "@heroui/card";
import { Chip } from "@heroui/chip";
import { createFileRoute, Link } from "@tanstack/react-router";
import { Tabs, Tab } from "@heroui/tabs";
import { Image } from "@heroui/image";
import { Button } from "@heroui/button";
import {
dataChipVida,
lifesafeCards,
servicesAccordionLife,
} from "~/content/vida";
import {
Table,
TableHeader,
TableColumn,
TableBody,
TableRow,
TableCell,
// getKeyValue,
} from "@heroui/table";
import { Accordion, AccordionItem } from "@heroui/accordion";
import ButtonCall from "~/components/ButtonCall";
import { seo } from "~/utils/seo";
import appCss from "~/styles/app.css?url";
const columns = [
{
key: "a",
label: "",
},
{
key: "basico",
label: "Plan Básico",
},
{
key: "estandar",
label: "Plan Esencial",
},
];
export const Route = createFileRoute("/seguros/vida")({
head: () => ({
meta: [
{
charSet: "utf-8",
},
{
name: "viewport",
content: "width=device-width, initial-scale=1",
},
...seo({
title: "Victoria Seguros | Vida",
description: `La aseguradora de los seguros de vida`,
keywords:
"seguros, seguros en Alicante, seguros en Murcia, seguros en la Costa, seguros de decesos, seguros de vida, seguros de salud, seguro de fallecimiento, cobertura funeraria, asistencia en decesos, póliza de vida, póliza de salud, seguro médico, seguro familiar, protección financiera, seguro para extranjeros, seguro de accidentes, servicio funerario, cremación, inhumación, ataúd, seguros completos, seguros económicos, aseguradora en Alicante, aseguradora en Murcia",
}),
],
links: [
{ rel: "stylesheet", href: appCss },
{ rel: "manifest", href: "/site.webmanifest", color: "#fffff" },
{ rel: "icon", href: "/favicon.ico" },
],
}),
component: RouteComponent,
});
function RouteComponent() {
return (
<div className="flex justify-center">
<Card className="w-full max-w-5xl m-2 md:m-4 overflow-hidden">
<CardHeader className="block">
<h1 className="text-2xl font-bold">Seguro de Vida</h1>
<div className="flex gap-2 flex-wrap my-4">
{dataChipVida?.map((cp) => (
<Chip
className="bg-primary-100"
key={cp.icon}
startContent={<span className={cp.label} />}
>
{cp.icon}
</Chip>
))}
</div>
</CardHeader>
<CardBody>
<p className="font-semibold mx-4">
Garantiza el futuro de tu familia protegiéndola en caso de
fallecimiento o invalidez, con el Seguro de Vida de Helvetia. e ofrecemos coberturas básicas de fallecimiento e
invalidez, junto con otras más innovadoras que aumentan tu nivel de
protección en caso de accidente o accidente de circulación. Calcula
el precio de tu seguro de vida y protege a los que más quieres.
</p>
<div className="flex gap-2 justify-center mb-5">
<ButtonCall secure="vida" />
</div>
<div className="mx-4">
<Tabs aria-label="Options" className="flex justify-center">
<Tab title="Coberturas">
<Table aria-label="Example table with dynamic content">
<TableHeader>
{columns.map((column) => (
<TableColumn key={column.key}>{column.label}</TableColumn>
))}
</TableHeader>
<TableBody>
<TableRow key={"incendio"}>
<TableCell className="font-semibold">
Fallecimiento por cualquier causa
</TableCell>
<TableCell>
<span className="iconify solar--shield-check-bold-duotone size-6 text-green-500"></span>
</TableCell>
<TableCell>
<span className="iconify solar--shield-check-bold-duotone size-6 text-green-500"></span>
</TableCell>
</TableRow>
<TableRow key={"servicio-asistencia"}>
<TableCell className="font-semibold">
Capital adicional si fallecen de ambos cónyuges por
accidente
</TableCell>
<TableCell>
<span className="iconify solar--shield-check-bold-duotone size-6 text-green-500"></span>
</TableCell>
<TableCell>
<span className="iconify solar--shield-check-bold-duotone size-6 text-green-500"></span>
</TableCell>
</TableRow>
<TableRow key={"responsabilidad-civil"}>
<TableCell className="font-semibold">
Helvetia Cuidados
</TableCell>
<TableCell>
<span className="iconify solar--shield-check-bold-duotone size-6 text-green-500"></span>
</TableCell>
<TableCell>
<span className="iconify solar--shield-check-bold-duotone size-6 text-green-500"></span>
</TableCell>
</TableRow>
<TableRow key={"alimento-refrigerados"}>
<TableCell className="font-semibold">
Invalidez Absoluta y Permanente por cualquier causa
</TableCell>
<TableCell>
<span className="iconify solar--shield-cross-bold-duotone size-6 text-red-500"></span>
</TableCell>
<TableCell>
<span className="iconify solar--shield-check-bold-duotone size-6 text-green-500"></span>
</TableCell>
</TableRow>
</TableBody>
</Table>
</Tab>
</Tabs>
</div>
<Card shadow="sm" className="my-8 gap-2">
<CardBody className="gap-6 grid md:grid-cols-2">
<div className="grid gap-2">
<h1 className="text-xl font-semibold">
Helvetia Cuidados
</h1>
<p className="text-">
Tu app de hábitos de vida saludable con la que conseguirás
descuentos en tu póliza mientras te cuidas más que nunca.
</p>
<Button
as={Link}
to="https://apps.apple.com/es/app/cuidados-victoria seguros/id1396448560"
startContent={
<span className="iconify size-6 cib--app-store" />
}
color="secondary"
>
AppStore - Apple
</Button>
<Button
as={Link}
to="https://play.google.com/store/apps/details?id=com.inithealth.victoria seguros"
startContent={
<span className="iconify size-6 cib--google-play" />
}
color="secondary"
>
PlayStore - Android
</Button>
</div>
<div className="grid place-content-center">
<Image
isZoomed
width={400}
height={300}
src="https://images.unsplash.com/photo-1522125670776-3c7abb882bc2?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
alt="prueba"
/>
</div>
</CardBody>
</Card>
</CardBody>
<CardFooter className="block">
<h1 className="text-xl mb-6 font-semibold text-purple-700 text-center">
Y además... Todos estos servicios de salud y bienestar{" "}
</h1>
<p className="text-sm">
Victoria Seguros Cuidados te ofrece un programa de bienestar
personalizado con consejos prácticos, contenido divertido y retos
semanales y mensuales para ayudarte a cuidarte con facilidad y
mejorar tu salud física y mental. Además, recibirás los siguientes
servicios exclusivos.
</p>
<div className="grid md:grid-cols-4 gap-4">
{lifesafeCards?.map((pc) => (
<Card className="border-none" shadow="none">
<CardHeader className="items-center">
<h3 className="text-lg font-semibold">{pc.title}</h3>
<span
className={`${pc.icon} size-20 absolute top-0 right-5 text-purple-500/15`}
/>
</CardHeader>
<CardBody>
<p className="text-sm">{pc.description}</p>
</CardBody>
</Card>
))}
</div>
<Card shadow="sm" className="my-8 gap-2">
<CardBody className="gap-6 grid md:grid-cols-2">
<div className="grid gap-2">
<h1 className="text-xl font-semibold">
¿No encuentras las garantías o coberturas que necesitas?{" "}
</h1>
<p className="text-md">
En Victoria Seguros, ofrecemos más opciones para su seguro de vida. Puede consultarnos si ofrecemos coberturas como incapacidad temporal (para autónomos) o enfermedad grave, así como coberturas superiores a 150.000 o cobertura profesional de alto riesgo. Contáctenos y revisaremos su caso gratuitamente.
</p>
<Button>Contacta con nosotros</Button>
</div>
<div className="grid place-content-center">
<Image
isZoomed
width={400}
height={300}
src="https://images.unsplash.com/photo-1522125670776-3c7abb882bc2?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
alt="prueba"
/>
</div>
</CardBody>
</Card>
<h1 className="text-xl mb-6 font-semibold text-purple-700 text-center">
Preguntas frequentes
</h1>
<Accordion defaultExpandedKeys={["acceso-especialistas"]}>
{servicesAccordionLife?.map((ac) => (
<AccordionItem
key={ac.key}
aria-label={ac.title}
title={ac.title}
startContent={<span className={ac.icon} />}
classNames={{
title: "font-semibold",
}}
>
<p className="">{ac?.descripcion}</p>
</AccordionItem>
))}
</Accordion>
</CardFooter>
</Card>
</div>
);
}

13
src/ssr.tsx Normal file
View File

@ -0,0 +1,13 @@
/// <reference types="vinxi/types/server" />
import {
createStartHandler,
defaultStreamHandler,
} from '@tanstack/react-start/server'
import { getRouterManifest } from '@tanstack/react-start/router-manifest'
import { createRouter } from './router'
export default createStartHandler({
createRouter,
getRouterManifest,
})(defaultStreamHandler)

26
src/styles/app.css Normal file
View File

@ -0,0 +1,26 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
html {
color-scheme: light dark;
}
* {
@apply border-gray-200 dark:border-gray-800;
}
html,
body {
@apply text-gray-900 bg-gray-50 dark:bg-gray-950 dark:text-gray-200;
}
.using-mouse * {
outline: none !important;
}
}
.main-lay {
background-image: url("https://images.unsplash.com/photo-1475503572774-15a45e5d60b9?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D");
}

33
src/utils/seo.ts Normal file
View File

@ -0,0 +1,33 @@
export const seo = ({
title,
description,
keywords,
image,
}: {
title: string
description?: string
image?: string
keywords?: string
}) => {
const tags = [
{ title },
{ name: 'description', content: description },
{ name: 'keywords', content: keywords },
{ name: 'twitter:title', content: title },
{ name: 'twitter:description', content: description },
{ name: 'twitter:creator', content: '@tannerlinsley' },
{ name: 'twitter:site', content: '@tannerlinsley' },
{ name: 'og:type', content: 'website' },
{ name: 'og:title', content: title },
{ name: 'og:description', content: description },
...(image
? [
{ name: 'twitter:image', content: image },
{ name: 'twitter:card', content: 'summary_large_image' },
{ name: 'og:image', content: image },
]
: []),
]
return tags
}

17
src/utils/sitemap.ts Normal file
View File

@ -0,0 +1,17 @@
import { type FileRouteTypes } from "@/routeTree.gen";
import { Sitemap } from "tanstack-router-sitemap";
// This will become a string literal union of all your routes
export type TRoutes = FileRouteTypes["fullPaths"];
// Define your sitemap
export const sitemap: Sitemap<TRoutes> = {
siteUrl: "https://victoriaseguros.es",
defaultPriority: 0.5,
routes: {
"/seguros": {
priority: 1,
changeFrequency: "daily",
},
},
};

60
tailwind.config.js Normal file
View File

@ -0,0 +1,60 @@
// tailwind.config.js
import { heroui } from "@heroui/theme";
import { addIconSelectors } from "@iconify/tailwind";
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./public/**/*",
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
"./node_modules/@heroui/theme/dist/components/(accordion|alert|autocomplete|avatar|badge|breadcrumbs|button|calendar|card|checkbox|chip|code|date-input|date-picker|divider|drawer|dropdown|form|image|input|input-otp|kbd|link|listbox|menu|modal|navbar|pagination|popover|progress|radio|ripple|scroll-shadow|select|skeleton|slider|snippet|spinner|table|tabs|user|spacer).js",
],
theme: {
extend: {},
},
darkMode: "class",
plugins: [heroui(
{
"themes": {
"light": {
"colors": {
"primary": {
"DEFAULT": "#46a9b4",
'50': '#f1fafa',
'100': '#daf2f3',
'200': '#bae3e7',
'300': '#8bcfd5',
'400': '#46a9b4',
'500': '#3895a2',
'600': '#317b89',
'700': '#2e6470',
'800': '#2c545e',
'900': '#294650',
'950': '#172e35',
},
"secondary": {
"DEFAULT": "#946fa9",
'50': '#faf8fc',
'100': '#f4f0f7',
'200': '#ece3f1',
'300': '#dbcee4',
'400': '#c4add3',
'500': '#ad8dbf',
'600': '#946fa9',
'700': '#805d93',
'800': '#6c5079',
'900': '#584162',
'950': '#3a2744',
},
},
}
},
"layout": {
"disabledOpacity": "0.5"
}
},
), addIconSelectors(["solar", "cib"])],
};

4
tailwind.config.mjs Normal file
View File

@ -0,0 +1,4 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ['./src/**/*.{js,jsx,ts,tsx}'],
}

22
tsconfig.json Normal file
View File

@ -0,0 +1,22 @@
{
"include": ["**/*.ts", "**/*.tsx"],
"compilerOptions": {
"strict": true,
"esModuleInterop": true,
"jsx": "react-jsx",
"module": "ESNext",
"moduleResolution": "Bundler",
"lib": ["DOM", "DOM.Iterable", "ES2022"],
"isolatedModules": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"target": "ES2022",
"allowJs": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"paths": {
"~/*": ["./src/*"],
},
"noEmit": true
}
}